<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom"><title>fuzzy notepad - blog</title><link href="https://eev.ee/" rel="alternate"></link><link href="https://eev.ee/feeds/blog.atom.xml" rel="self"></link><id>https://eev.ee/</id><updated>2022-01-31T21:15:00-08:00</updated><entry><title>Monday Night Itch #1: Mystery Trap Adventure</title><link href="https://eev.ee/blog/2022/01/31/monday-night-itch-1-mystery-trap-adventure/" rel="alternate"></link><published>2022-01-31T21:15:00-08:00</published><updated>2022-01-31T21:15:00-08:00</updated><author><name>Eevee</name></author><id>tag:eev.ee,2022-01-31:/blog/2022/01/31/monday-night-itch-1-mystery-trap-adventure/</id><summary type="html">&lt;p&gt;&lt;strong&gt;Welcome&lt;/strong&gt; to Monday Night Itch, a harebrained scheme to encourage folks to play more non-&lt;span class="caps"&gt;AAA&lt;/span&gt; games by adding a touch of social gamification.  I thought I would be tweeting my adventures here, but I just had an experience so profound it can only be captured within a blog post.&lt;/p&gt;
</summary><content type="html">&lt;p&gt;&lt;strong&gt;Welcome&lt;/strong&gt; to Monday Night Itch, a harebrained scheme to encourage folks to play more non-&lt;span class="caps"&gt;AAA&lt;/span&gt; games by adding a touch of social gamification.  I thought I would be tweeting my adventures here, but I just had an experience so profound it can only be captured within a blog&amp;nbsp;post.&lt;/p&gt;


&lt;h2 id="the-rules"&gt;&lt;a class="toclink" href="#the-rules"&gt;The&amp;nbsp;rules&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;&lt;span class="dquo"&gt;&amp;#8220;&lt;/span&gt;Rules&amp;#8221; is a strong word, but&amp;nbsp;nevertheless:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Every Monday, find a game on &lt;a href="https://itch.io/"&gt;itch.io&lt;/a&gt;, and pay at least $2 for&amp;nbsp;it.&lt;/p&gt;
&lt;p&gt;You can buy a game with a price tag, or download a free game and leave a tip, but the point of this endeavor is to put money into more places in the ecosystem.  (Note that it &lt;em&gt;is&lt;/em&gt; possible, though uncommon, for a developer to disable payments&amp;nbsp;altogether.)&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Play&amp;nbsp;it.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Leave a nice&amp;nbsp;comment.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Tell at least one person what you played, and what you thought about&amp;nbsp;it.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;That&amp;#8217;s it.  Buy a game, play it, tell someone about it.  You can stream it, tweet it, screenshot it, or just tell your boyfriend about it.  You don&amp;#8217;t have to like&amp;nbsp;it&lt;/p&gt;
&lt;p&gt;Your score is how many times you&amp;#8217;ve done this, and your streak is how many weeks you&amp;#8217;ve done it in a&amp;nbsp;row.&lt;/p&gt;
&lt;h2 id="some-other-quick-tips-about-itch"&gt;&lt;a class="toclink" href="#some-other-quick-tips-about-itch"&gt;Some other quick tips about&amp;nbsp;itch&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;The &lt;a href="https://itch.io/app"&gt;itch app&lt;/a&gt; is cool.  It&amp;#8217;s a pretty thin wrapper around the website, but it adds automatic updating and big red &amp;#8220;Launch&amp;#8221; buttons and other stuff to make it feel a bit more like a Steam-ish thing.  Do keep in mind that devs can upload whatever they want, and sometimes the itch app gets&amp;nbsp;confused.&lt;/p&gt;
&lt;p&gt;If you&amp;#8217;re not a fan of running mystery software you downloaded from the Internet, you can just play web games and leave tips on&amp;nbsp;those.&lt;/p&gt;
&lt;p&gt;There are &lt;em&gt;a lot&lt;/em&gt; of &lt;span class="caps"&gt;NSFW&lt;/span&gt; games on itch, but they&amp;#8217;re hidden from the main browse pages by default.  You can enable them site-wide in your &lt;a href="https://itch.io/user/settings"&gt;user settings&lt;/a&gt;, or add &lt;code&gt;/nsfw&lt;/code&gt; to the end of a browse page &lt;span class="caps"&gt;URL&lt;/span&gt; (for example, &lt;code&gt;https://itch.io/games&lt;/code&gt; → &lt;code&gt;https://itch.io/games/nsfw&lt;/code&gt;) to force a list of &lt;em&gt;only&lt;/em&gt; &lt;span class="caps"&gt;NSFW&lt;/span&gt;&amp;nbsp;games.&lt;/p&gt;
&lt;h2 id="the-main-event"&gt;&lt;a class="toclink" href="#the-main-event"&gt;The main&amp;nbsp;event&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;I decided I wanted to reward Linux releases, and also chip a few bucks towards games with a price tag that aren&amp;#8217;t necessarily getting much exposure, so I went to the full list of &lt;a href="https://itch.io/games/newest/platform-linux/store"&gt;recent paid Linux games&lt;/a&gt;.  This is how I discovered &lt;a href="https://rvedastudios.itch.io/mystery-trap-adventure"&gt;Mystery Trap Adventure&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;I found myself &lt;em&gt;very&lt;/em&gt; much wanting to play this, but I also found myself wondering what sort of impact I should be trying for as the very first iteration of this project.  Would I torpedo it if I played a game made by a less experienced dev?  Are people looking to this expecting me to uncover unknown indie gems, like I&amp;#8217;m wandering a beach with a metal&amp;nbsp;detector?&lt;/p&gt;
&lt;p&gt;I checked the dev&amp;#8217;s itch profile and this is their &lt;em&gt;ninth&lt;/em&gt; project.  Every single previous work of their has only a single comment: from them, announcing that comments can be left below.  That&amp;#8217;s heartbreaking to me, and what made me absolutely sure I wanted to play this.  I want to make their&amp;nbsp;day.&lt;/p&gt;
&lt;p&gt;And then, dear reader, I felt ashamed.  Because who the fuck cares.  The world already has enough people who believe that indie games are only valuable if they create the illusion of an eight-digit budget, and I am not here to enable them.  Creative work does not need to be polished, mass-appeal, least common denominator stuff handed down from heaven by a billion-dollar international corporation in order to be interesting or&amp;nbsp;worthwhile.&lt;/p&gt;
&lt;p&gt;But more importantly, it&amp;#8217;s my thing and I&amp;#8217;m gonna do whatever the hell I&amp;nbsp;want.&lt;/p&gt;
&lt;p&gt;&lt;div class="prose-full-illustration"&gt;&lt;img src="/media/monday-night-itch/001-mystery-trap-adventure/title.jpg" alt="The title screen for Mystery Trap Adventure: a collage of mismatched artwork on a nearly cyan background"&gt;&lt;/div&gt;&lt;/p&gt;
&lt;p&gt;And so, Mystery Trap&amp;nbsp;Adventure.&lt;/p&gt;
&lt;p&gt;The first thing to note is that the game does not, in fact, have a Linux release.  I did strongly suspect this, since a single download is flagged as all of Windows, Mac, and Linux, but the only way to be sure was to buy it.  (They&amp;#8217;re asking $4; I paid them $10.)  Even Wine had trouble with it, for some reason, so I had to play it on our Windows media&amp;nbsp;center.&lt;/p&gt;
&lt;p&gt;It&amp;#8217;s a sidescrolling platformer where you play as a dragon; you can jump about one tile high (roughly your own height) and shoot fireballs (useful for destroying bricks and defeating the boss).  The main obstacle is spikes, which kill you&amp;nbsp;instantly.&lt;/p&gt;
&lt;p&gt;Right at the beginning, there&amp;#8217;s a block you have to jump on top of, and it was very obvious that I sort of &amp;#8220;stuck&amp;#8221; to the side of it if I touched it.  I thought at first that this was the result of a common platforming gotcha: if you model the player as a dynamic body and implement movement (including air control) as a force on them, then they will stick to walls as long as the corresponding direction is held.  This happens because forces on dynamic bodies are external, as though a giant ghost hand were pushing them — so if a player is trying to air control into a wall, the &lt;em&gt;friction against the wall&lt;/em&gt; will hold them in place, just as if you were holding a book against a wall with your&amp;nbsp;hand.&lt;/p&gt;
&lt;p&gt;(Solving that problem is beyond the scope of this post,&amp;nbsp;sorry.)&lt;/p&gt;
&lt;p&gt;Okay, common pitfall, no big deal.  I wander ahead a bit.  I encounter a slice of watermelon, which allows me to teleport a short distance &lt;em&gt;once&lt;/em&gt;.  I screw this up the first time while messing with the controls — there&amp;#8217;s a wall directly in front of it, so the teleport must be used to skip past that wall — and have to&amp;nbsp;restart.&lt;/p&gt;
&lt;p&gt;Now something interesting happens.  I&amp;#8217;m in a pit with walls on both sides.  I can&amp;#8217;t teleport again, and even if I could, there are spikes beyond the next wall, so that would kill me&amp;nbsp;immediately.&lt;/p&gt;
&lt;p&gt;&lt;div class="prose-full-illustration"&gt;&lt;img src="/media/monday-night-itch/001-mystery-trap-adventure/pit.jpg" alt="A screenshot of the situation just described"&gt;&lt;/div&gt;&lt;/p&gt;
&lt;p&gt;It dawns on me that this microscopic game has &lt;em&gt;walljumping&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;I&amp;#8217;m still fairly certain that the player character is a dynamic body, but now I wonder: is the wall stickiness actually due to the friction interaction, or is it a deliberate feature to enable&amp;nbsp;walljumping?&lt;/p&gt;
&lt;p&gt;Or, perhaps more likely, is it &lt;em&gt;both&lt;/em&gt;?  Did the developer trip over this pitfall, and decide to make a gameplay feature out of it?  It almost seems unbelievable.  I wouldn&amp;#8217;t consider walljumping a &lt;em&gt;basic&lt;/em&gt; platforming ability, and it&amp;#8217;s not obvious how to solve the friction problem, but it seems that this relatively new developer may have solved both problems by simply smashing them&amp;nbsp;together.&lt;/p&gt;
&lt;p&gt;And if that&amp;#8217;s the case, dearest reader: I &lt;em&gt;fucking love it&lt;/em&gt;.  That is the true spirit of game development, I think — you have a big complicated simulation you want to make, and you have a big complicated engine that you want to make do it, and you have to kinda mold both of them into fitting better with the&amp;nbsp;other.&lt;/p&gt;
&lt;p&gt;I don&amp;#8217;t know.  I could be completely wrong about this came to be.  Or they could have copy/pasted from someone else who had this idea.  Either way, it made me smile to&amp;nbsp;see.&lt;/p&gt;
&lt;p&gt;The walljumping controls are, ahem, not exactly intuitive, which is why it took me nonzero time to realize it was an ability at all.  But honestly, I liked that too.  Nowadays, everyone knows exactly how every platforming ability is &amp;#8220;supposed&amp;#8221; to work, because devs are all copying the same ideas from each other that have been refined over a thousand different iterations.  This reminded me of playing games in the early and mid 90s, before everything had standardized as much, when part of the game itself was just working out the right muscle memory to make the right things happen.  It&amp;#8217;s surprising to find nostalgia in a game because it&amp;#8217;s &lt;em&gt;not&lt;/em&gt; like others I&amp;#8217;ve played before, but there it was.  Working out the right timing without any visual cues felt like a puzzle in itself, and getting out of the pit without landing in the spikes was remarkably satisfying.  (If it helps: I used different hands for movement and jumping, and I landed on top of the right wall before trying to jump over the&amp;nbsp;spikes.)&lt;/p&gt;
&lt;p&gt;Beyond this, the tone changes somewhat to &lt;span class="caps"&gt;IWBTG&lt;/span&gt;-esque traps with no telegraphing.  Walking directly to the right will cause spikes to appear from the ground, killing you instantly.  Thankfully there aren&amp;#8217;t too many of these, and the game is very short, so simply memorizing the handful of places they appear is easy&amp;nbsp;enough.&lt;/p&gt;
&lt;p&gt;I have less to say about the rest of the game; you get another quirky powerup you only use once, dodge another couple surprise traps, and face a single boss.  The boss is a very large human warrior dude who walks straight at you and swings his sword, which kills you.  There&amp;#8217;s another fruit above you, but it seems out of reach.  He is definitely too tall to jump over.  The only solution I found is to simply spam fireballs at him before he can reach you, but I don&amp;#8217;t know if this is intended.  It seems like it can&amp;#8217;t be, since his &amp;#8220;health bar&amp;#8221; takes the form of a grid of his face behind him, and from where you enter the area, you can&amp;#8217;t actually see the whole grid?  So surely I&amp;#8217;m supposed to be able to get further to the right?  But I don&amp;#8217;t&amp;nbsp;know.&lt;/p&gt;
&lt;hr /&gt;
&lt;p&gt;I finished the game and came back to the following reply to my original thread about this whole&amp;nbsp;concept:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;most, i.e. all, small Indy games are&amp;nbsp;terrible.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;What a snotty, entitled, mean-spirited sentiment.  As if the very existence of a game with lower production values than Resident Evil 8 were a personal offense.  It seems to be fairly common, too, and I just do not understand it.  Small indie games aren&amp;#8217;t trying to squeeze you for more money, lure you in with gambling, exploit your friendships, make your entire life revolve around them.  They&amp;#8217;re just &lt;em&gt;there&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;This attitude is like showing up to everyone who mentions YouTube just to proclaim that everything on it sucks, because Paramount movies are better.  That&amp;#8217;s great, no one asked!  Sometimes I just want to see a seven-second clip of a kitten filmed in a dark room by a $20 phone, because dammit, kittens are still fun to watch.  No one makes a point of dunking on videos like that, so I don&amp;#8217;t know why anyone is so harsh on amateur games either.  &lt;em&gt;Especially&lt;/em&gt; when making games is so much more&amp;nbsp;difficult!&lt;/p&gt;
&lt;p&gt;Mystery Trap Adventure is that video.  Someone had an idea, worked out how to express it, and put it out into the world just because they wanted to.  I don&amp;#8217;t expect anyone else to buy it or play it; I just want you to know that &lt;em&gt;I&lt;/em&gt; did, and it made me smile for a few&amp;nbsp;minutes.&lt;/p&gt;</content><category term="blog"></category><category term="games"></category><category term="monday night itch"></category></entry><entry><title>Goodbye, Pearl</title><link href="https://eev.ee/blog/2022/01/25/goodbye-pearl/" rel="alternate"></link><published>2022-01-25T22:57:00-08:00</published><updated>2022-01-25T22:57:00-08:00</updated><author><name>Eevee</name></author><id>tag:eev.ee,2022-01-25:/blog/2022/01/25/goodbye-pearl/</id><summary type="html">&lt;p&gt;&lt;div class="prose-full-illustration"&gt;&lt;img alt="Pearl laying on carpet, bathed in a sunbeam that highlights her peach fuzz" src="/media/2022-01-goodbye-pearl/pearl-radiant.jpg"/&gt;&lt;/div&gt;&lt;/p&gt;
&lt;p&gt;A Chronicling of the Lyfe and Times of one Miss Pearl Twig Woods, who has Passed at a Young Age from Troubles of the Heart.  She is survived by Anise, her Arch Nemesis; Cheeseball, her Adoptive Ruffian; and Napoleon, her Star-Crossed Suitor for Whom she Longed from Afar.&lt;/p&gt;
</summary><content type="html">&lt;p&gt;&lt;div class="prose-full-illustration"&gt;&lt;img src="/media/2022-01-goodbye-pearl/pearl-radiant.jpg" alt="Pearl laying on carpet, bathed in a sunbeam that highlights her peach fuzz"&gt;&lt;/div&gt;&lt;/p&gt;
&lt;p&gt;A Chronicling of the Lyfe and Times of one Miss Pearl Twig Woods, who has Passed at a Young Age from Troubles of the Heart.  She is survived by Anise, her Arch Nemesis; Cheeseball, her Adoptive Ruffian; and Napoleon, her Star-Crossed Suitor for Whom she Longed from&amp;nbsp;Afar.&lt;/p&gt;


&lt;p&gt;&lt;div class="gallery"&gt;
&lt;a href="/media/2022-01-goodbye-pearl/pearl-sit-hat.jpg" class="photo" title="Hello.  This hat is for Pearl now thank you. "&gt;&lt;img src="/media/2022-01-goodbye-pearl/pearl-sit-hat_m.jpg" alt="Pearl sits on a hat on the dining room table, regarding the camera with a slightly tilted head"&gt;&lt;/a&gt;
&lt;a href="/media/2022-01-goodbye-pearl/pearl-cool-arm.jpg" class="photo" title="What a cool arm.  That&amp;#x27;s how you know this is a cat with an attitude. "&gt;&lt;img src="/media/2022-01-goodbye-pearl/pearl-cool-arm_m.jpg" alt="Pearl settled across Ash&amp;#x27;s knees and arms, with one paw tucked in a way that looks like crossed arms"&gt;&lt;/a&gt;
&lt;a href="/media/2022-01-goodbye-pearl/pearl-happy.jpg" class="photo" title="Delight. "&gt;&lt;img src="/media/2022-01-goodbye-pearl/pearl-happy_m.jpg" alt="Pearl looks very happy"&gt;&lt;/a&gt;
&lt;a href="/media/2022-01-goodbye-pearl/pearl-depth-of-field.jpg" class="photo" title=""&gt;&lt;img src="/media/2022-01-goodbye-pearl/pearl-depth-of-field_m.jpg" alt=""&gt;&lt;/a&gt;
&lt;/div&gt;&lt;/p&gt;
&lt;p&gt;Pearl is&amp;#8230;  difficult to describe.  She had such a strong, vibrant&amp;nbsp;personality.&lt;/p&gt;
&lt;p&gt;She was &lt;em&gt;lovely&lt;/em&gt;, that&amp;#8217;s for certain.  She loved everyone she met.  And while various people — friends, vets, etc. — have met our cats and always &lt;em&gt;liked&lt;/em&gt; them, I don&amp;#8217;t believe anyone has met Pearl and not &lt;em&gt;adored&lt;/em&gt; her.  Anise will check out your stuff and perhaps jump on you; Cheeseball will do antics for you and rub on your leg; but Pearl would accept you into her life and be very directly, personally affectionate with you specifically.  She made you feel&amp;nbsp;special.&lt;/p&gt;
&lt;p&gt;At the same time, she was very fussy, very particular, and had a very strong sense of&amp;#8230;  her place in the world, I suppose.  If she liked something, she would be having it.  If she didn&amp;#8217;t like something, she would make that exceptionally clear.  She was never &lt;em&gt;mean&lt;/em&gt;, but she would be very vocal about her&amp;nbsp;boundaries.&lt;/p&gt;
&lt;p&gt;It wasn&amp;#8217;t uncommon to wake up to Pearl repeatedly headbutting me right in the face, pressing her head up under my chin, or giving me a nuzzle with the entire length of her body, purring all the while.  If she was happy to see you, she made an entire production out of it.  It wasn&amp;#8217;t just us; guests who slept on the couch also got the Pearl wake-up&amp;nbsp;call.&lt;/p&gt;
&lt;p&gt;It was &lt;em&gt;also&lt;/em&gt; not uncommon to wake up because Pearl had decided that she needed my pillow, and somehow this very small cat took up the entire thing.  I couldn&amp;#8217;t &lt;em&gt;move&lt;/em&gt; her; trying to displace her from a comfortable spot would generally earn you a sad, offended meow, after which you felt guilty for even having entertained the notion in the first&amp;nbsp;place.&lt;/p&gt;
&lt;p&gt;One of her particular quirks was to often &amp;#8220;bury&amp;#8221; her food when she was done with it, or at least paw fruitlessly at nearby carpet.  On its own, this is endearing but not unusual — burying leftovers is a common cat instinct, even if we&amp;#8217;ve not seen it in our other cats.  What made it a uniquely Pearl trait was that she would &lt;em&gt;also&lt;/em&gt; perform this ritual if offered something she didn&amp;#8217;t want at all.  I laughed every time; it was such an audacious way to indicate utter disinterest.  Take it away, please.  Put it in a hole, if you&amp;nbsp;would.&lt;/p&gt;
&lt;p&gt;She got, more or less, everything she wanted.  If she claimed a spot, everything about her expression and body language indicated it was clearly hers, even if that spot was your body.  (Naturally, if you moved too much or even sneezed suddenly, she would tell you off for that too.)  If she wanted to ride on your shoulders, that&amp;#8217;s what would be happening.  If it was time to feed her and she was too comfortable in her cat tree, well, we&amp;#8217;d just have to hold the food up for her.  She had a way of looking very pleased with herself that was impossible to argue&amp;nbsp;with.&lt;/p&gt;
&lt;p&gt;&lt;div class="gallery"&gt;
&lt;a href="/media/2022-01-goodbye-pearl/pearl-pillow-theft.jpg" class="photo" title="A not-uncommon configuration. "&gt;&lt;img src="/media/2022-01-goodbye-pearl/pearl-pillow-theft_m.jpg" alt="Pearl rests comfortably on a pillow, while Ash is asleep below it, having conceded it entirely to a small cat"&gt;&lt;/a&gt;
&lt;a href="/media/2022-01-goodbye-pearl/pearl-beanbag.jpg" class="photo" title="I guess this belongs to Pearl now. "&gt;&lt;img src="/media/2022-01-goodbye-pearl/pearl-beanbag_m.jpg" alt="Pearl sits right in the center of a beanbag that is vastly bigger than her, leaving no real space for anyone else"&gt;&lt;/a&gt;
&lt;a href="/media/2022-01-goodbye-pearl/pearl-claws-1.jpg" class="photo" title="Pearl could be very frumpy about getting her claws trimmed. "&gt;&lt;img src="/media/2022-01-goodbye-pearl/pearl-claws-1_m.jpg" alt="Pearl is held up in Ash&amp;#x27;s lap, making a very frumpy face, while Ash trims her claws"&gt;&lt;/a&gt;
&lt;a href="/media/2022-01-goodbye-pearl/pearl-claws-2.jpg" class="photo" title="Mmmmmnnnn...  I dunno. "&gt;&lt;img src="/media/2022-01-goodbye-pearl/pearl-claws-2_m.jpg" alt="Pearl, a moment later, tries to lean her way out of Ash&amp;#x27;s lap"&gt;&lt;/a&gt;
&lt;a href="/media/2022-01-goodbye-pearl/pearl-shoulder-ash.jpg" class="photo" title="Pearl lives here now thank you. "&gt;&lt;img src="/media/2022-01-goodbye-pearl/pearl-shoulder-ash_m.jpg" alt="Pearl washes her face while laying across Ash&amp;#x27;s shoulders"&gt;&lt;/a&gt;
&lt;a href="/media/2022-01-goodbye-pearl/pearl-pampered.jpg" class="photo" title="Pearl the pampered princess. "&gt;&lt;img src="/media/2022-01-goodbye-pearl/pearl-pampered_m.jpg" alt="Ash sits on a couch with a sketchbook in their lap and Pearl, wrapped in a blanket, under one arm, getting her chin rubbed"&gt;&lt;/a&gt;
&lt;a href="/media/2022-01-goodbye-pearl/pearl-nuzzle-blur.jpg" class="photo" title="This is tricky to get a photo of, but it&amp;#x27;s also just the Pearl experience. "&gt;&lt;img src="/media/2022-01-goodbye-pearl/pearl-nuzzle-blur_m.jpg" alt="Pearl steps down over my shoulder, rubbing her whole body against my face as she goes, displacing my glasses"&gt;&lt;/a&gt;
&lt;a href="/media/2022-01-goodbye-pearl/pearl-nuzzle-nose.jpg" class="photo" title="It&amp;#x27;s like being punched, over and over, by a very tiny and lovely fist. "&gt;&lt;img src="/media/2022-01-goodbye-pearl/pearl-nuzzle-nose_m.jpg" alt="Pearl headbutts my nose so hard it bends to one side"&gt;&lt;/a&gt;
&lt;/div&gt;&lt;/p&gt;
&lt;p&gt;I first met Pearl in 2014, shortly after we moved to Las Vegas.  She was &lt;em&gt;tiny&lt;/em&gt;, even for a kitten, and apparently the runt of her litter.  I don&amp;#8217;t remember what specifically compelled Ash to adopt another cat, except that they love cats, but what a&amp;nbsp;selection.&lt;/p&gt;
&lt;p&gt;I cannot stress enough how small she was.  You know those solid wood desks that have a column of drawers built into them on one side?  You know how they often have a little decorative shape carved out at the bottom with molded edges?  Pearl could crawl into that space.  I couldn&amp;#8217;t believe it the first time I saw it; the gap is so short that I&amp;#8217;d never even thought to categorize it as a &lt;em&gt;space&lt;/em&gt;, let alone one a cat might enter, but she slunk into it like it was nothing.  I was so worried we&amp;#8217;d have to move the desk somehow to get her out, but she usually turned around and came right out again.  I still remember the very last time she did it — I could tell she was having to shimmy a bit to fit in there, and she must have noticed too, because I never saw her even try it&amp;nbsp;again.&lt;/p&gt;
&lt;p&gt;The other cats had somewhat mixed feelings.  Napoleon didn&amp;#8217;t like her at all and hissed directly in her face, but&amp;#8230;  after that, I don&amp;#8217;t remember any bad reactions from him at all, so I guess he warmed up quick.  Anise did not seem to understand what a kitten was, tried to play with her, and then acted very confused when that didn&amp;#8217;t seem to work.  And&amp;nbsp;Twigs&amp;#8230;&lt;/p&gt;
&lt;p&gt;Oh, Twigs.  Twigs was &lt;em&gt;jealous&lt;/em&gt;.  He had always been Ash&amp;#8217;s cat, he had &lt;em&gt;made himself&lt;/em&gt; Ash&amp;#8217;s cat, and he very quickly inferred that Pearl was a threat to his position.  Another cat!  In Ash&amp;#8217;s lap!&amp;nbsp;Unthinkable!&lt;/p&gt;
&lt;p&gt;On one particular night Ash had barred Twigs from the bedroom to sleep with just Pearl, but came downstairs to visit the kitchen.  Twigs ran up to them, looked them dead in the eye, and let out a huge sad wail to convey his feelings about the depths of this&amp;nbsp;betrayal.&lt;/p&gt;
&lt;p&gt;They let him into the bedroom after that, but he opted to sit across the room and stare daggers at Pearl, moving a little closer every half hour until he was on the far corner of the bed.  Just&amp;nbsp;staring.&lt;/p&gt;
&lt;p&gt;Ash eventually had to bribe him by putting some cottage cheese on Pearl&amp;#8217;s head, after which he decided Pearl was okay.  Also he found out that he could fit himself in Ash&amp;#8217;s lap alongside Pearl, so that probably&amp;nbsp;helped.&lt;/p&gt;
&lt;p&gt;&lt;div class="gallery"&gt;
&lt;a href="/media/2022-01-goodbye-pearl/pearl-kitten-meeting-napoleon-1.jpg" class="photo" title="Pearl meets Napoleon, our largest cat, for the first time. "&gt;&lt;img src="/media/2022-01-goodbye-pearl/pearl-kitten-meeting-napoleon-1_m.jpg" alt="Pearl, a very small sphynx kitten, sniffing the nose of a furred cat twice her height"&gt;&lt;/a&gt;
&lt;a href="/media/2022-01-goodbye-pearl/pearl-kitten-meeting-napoleon-2.jpg" class="photo" title="Napoleon is not impressed. "&gt;&lt;img src="/media/2022-01-goodbye-pearl/pearl-kitten-meeting-napoleon-2_m.jpg" alt="Same pair as previous photo, but Napoleon is hissing and Pearl is flinching"&gt;&lt;/a&gt;
&lt;a href="/media/2022-01-goodbye-pearl/pearl-kitten-anise-1.jpg" class="photo" title="Pearl is so small she has to climb the stairs one at a time. "&gt;&lt;img src="/media/2022-01-goodbye-pearl/pearl-kitten-anise-1_m.jpg" alt="Tiny kitten climbing up a single step of a flight of stairs, while our black sphynx Anise sits and watches"&gt;&lt;/a&gt;
&lt;a href="/media/2022-01-goodbye-pearl/pearl-kitten-anise-2.jpg" class="photo" title="Anise is not sure what to do with this. "&gt;&lt;img src="/media/2022-01-goodbye-pearl/pearl-kitten-anise-2_m.jpg" alt="Anise straddles a couple higher steps and leans down to sniff Pearl"&gt;&lt;/a&gt;
&lt;a href="/media/2022-01-goodbye-pearl/pearl-kitten-anise-3.jpg" class="photo" title="Maybe if he taps it, something will happen. "&gt;&lt;img src="/media/2022-01-goodbye-pearl/pearl-kitten-anise-3_m.jpg" alt="Anise lightly paps Pearl&amp;#x27;s head with one paw"&gt;&lt;/a&gt;
&lt;a href="/media/2022-01-goodbye-pearl/pearl-kitten-anise-4.jpg" class="photo" title="Very suspicious. "&gt;&lt;img src="/media/2022-01-goodbye-pearl/pearl-kitten-anise-4_m.jpg" alt="Anise recoils when Pearl looks at him"&gt;&lt;/a&gt;
&lt;a href="/media/2022-01-goodbye-pearl/pearl-kitten-anise-5.jpg" class="photo" title="Tactical retreat?  I love this photo, I have no idea what he was doing. "&gt;&lt;img src="/media/2022-01-goodbye-pearl/pearl-kitten-anise-5_m.jpg" alt="Pearl yawns while Anise peeks around a corner at her from the top of the stairs"&gt;&lt;/a&gt;
&lt;a href="/media/2022-01-goodbye-pearl/pearl-kitten-twigs-bribe-1.jpg" class="photo" title="Twigs regards Pearl with deep suspicion. "&gt;&lt;img src="/media/2022-01-goodbye-pearl/pearl-kitten-twigs-bribe-1_m.jpg" alt="Pearl lays sleepily on Ash while Twigs sits on their desk and looks at her with a somewhat sour expression"&gt;&lt;/a&gt;
&lt;a href="/media/2022-01-goodbye-pearl/pearl-kitten-twigs-bribe-2.jpg" class="photo" title="Twigs decides Pearl can&amp;#x27;t be that bad if she tastes like cheese. "&gt;&lt;img src="/media/2022-01-goodbye-pearl/pearl-kitten-twigs-bribe-2_m.jpg" alt="Twigs licks some cottage cheese off of Pearl&amp;#x27;s head"&gt;&lt;/a&gt;
&lt;a href="/media/2022-01-goodbye-pearl/pearl-kitten-stretched.jpg" class="photo" title="Pearl loves to be comfortable. "&gt;&lt;img src="/media/2022-01-goodbye-pearl/pearl-kitten-stretched_m.jpg" alt="Pearl lays outstretched alongside Ash&amp;#x27;s forearm, barely longer or wider than it"&gt;&lt;/a&gt;
&lt;a href="/media/2022-01-goodbye-pearl/pearl-kitten-slipper.jpg" class="photo" title="She was so tiny that she could just sit in one of my slippers, like it was her personal cat bed. "&gt;&lt;img src="/media/2022-01-goodbye-pearl/pearl-kitten-slipper_m.jpg" alt="A very small Pearl settles on a slipper, which is very slightly bigger than her"&gt;&lt;/a&gt;
&lt;/div&gt;&lt;/p&gt;
&lt;p&gt;Oh, and she loved to be cozy.  She &lt;em&gt;loved&lt;/em&gt; to be cozy.  Sphynxes are naturally drawn to warmth, of course, but Pearl elevated it to an artform.  If I&amp;#8217;m propped up in bed, Anise might stand next to me to look at the covers expectantly, or he might just lay down nearby.  Pearl would stand right on top of me and &lt;em&gt;pull&lt;/em&gt; at the covers with impressive force until I lifted them for her, let her lay on my chest, and tucked her&amp;nbsp;in.&lt;/p&gt;
&lt;p&gt;We&amp;#8217;d often find Pearl very awkwardly tucked under the edge of a blanket somewhere, having attempted to insert herself beneath it with mixed success.  We described this as Pearl doing it all by herself, and complimented her on how talented she was, and then fixed the blanket for&amp;nbsp;her.&lt;/p&gt;
&lt;p&gt;&lt;div class="gallery"&gt;
&lt;a href="/media/2022-01-goodbye-pearl/pearl-doing-it-2.jpg" class="photo" title="She did it all by herself! "&gt;&lt;img src="/media/2022-01-goodbye-pearl/pearl-doing-it-2_m.jpg" alt="Pearl lays tucked within a blanket, curled around her like a nest"&gt;&lt;/a&gt;
&lt;a href="/media/2022-01-goodbye-pearl/pearl-doing-it-3.jpg" class="photo" title="She&amp;#x27;s SO talented. "&gt;&lt;img src="/media/2022-01-goodbye-pearl/pearl-doing-it-3_m.jpg" alt="Pearl is underneath a blanket but her entire back half is sticking out"&gt;&lt;/a&gt;
&lt;a href="/media/2022-01-goodbye-pearl/pearl-doing-it-4.jpg" class="photo" title="She tried her very best. "&gt;&lt;img src="/media/2022-01-goodbye-pearl/pearl-doing-it-4_m.jpg" alt="Pearl lays beneath a single folded-over corner of an electric blanket, which barely covers her at all"&gt;&lt;/a&gt;
&lt;/div&gt;&lt;/p&gt;
&lt;p&gt;We have heater vents in the floor, and one of her favorite pastimes was to sit on one of those, often covering the entire thing, and be gently toasted from below.  Sometimes Anise would see what a great idea that was and try to share it and they would end up&amp;nbsp;squabbling.&lt;/p&gt;
&lt;p&gt;If there was a sunbeam to be found, Pearl would find it.  Much like with vents, she didn&amp;#8217;t like to share sunbeams, even if they were half the width of the room.  She found it first, you&amp;nbsp;see.&lt;/p&gt;
&lt;p&gt;Other places she discovered that were lovely and toasty included: in front of the fridge where the warm air vented out from the bottom; straddling the &lt;span class="caps"&gt;PS4&lt;/span&gt; so the fan blew onto her tummy; next to or underneath my laptop; on top of my computer case which has a fan vent on top; in front of the heat dish we got while our furnace wasn&amp;#8217;t working; and in a laundry hamper full of freshly-dried&amp;nbsp;laundry.&lt;/p&gt;
&lt;p&gt;She liked to go outside, too, during the summer.  All of our cats are indoor-only, but once in a while we&amp;#8217;ll take the more well-behaved ones (not Cheeseball) into the backyard to wander around on the porch and sniff things and enjoy the sun and look at a&amp;nbsp;bird.&lt;/p&gt;
&lt;p&gt;&lt;div class="gallery"&gt;
&lt;a href="/media/2022-01-goodbye-pearl/pearl-vent.jpg" class="photo" title="One of Pearl&amp;#x27;s favorite places. "&gt;&lt;img src="/media/2022-01-goodbye-pearl/pearl-vent_m.jpg" alt="Pearl sits atop a heater vent in the floor"&gt;&lt;/a&gt;
&lt;a href="/media/2022-01-goodbye-pearl/pearl-sunbeam.jpg" class="photo" title="This is a pretty good summary of Pearl. "&gt;&lt;img src="/media/2022-01-goodbye-pearl/pearl-sunbeam_m.jpg" alt="Pearl sits in a small square sunbeam, roughly the size of her silhouette, in the middle of the floor"&gt;&lt;/a&gt;
&lt;a href="/media/2022-01-goodbye-pearl/pearl-fridge-vent.jpg" class="photo" title="Pearl no it&amp;#x27;s so dusty down there "&gt;&lt;img src="/media/2022-01-goodbye-pearl/pearl-fridge-vent_m.jpg" alt="Pearl sits, paws folded, next to the vent at the bottom of the fridge"&gt;&lt;/a&gt;
&lt;a href="/media/2022-01-goodbye-pearl/pearl-heat-dish.jpg" class="photo" title="Honestly kind of amazed she never got a tan from this.  Also pictured: string, her favorite. "&gt;&lt;img src="/media/2022-01-goodbye-pearl/pearl-heat-dish_m.jpg" alt="Pearl sits right in front of a heat dish"&gt;&lt;/a&gt;
&lt;a href="/media/2022-01-goodbye-pearl/pearl-computer-vent.jpg" class="photo" title="Pearl please I am trying to compile GZDoom. "&gt;&lt;img src="/media/2022-01-goodbye-pearl/pearl-computer-vent_m.jpg" alt="Pearl sits on top of my computer case with her front half mostly overlapping a fan vent"&gt;&lt;/a&gt;
&lt;a href="/media/2022-01-goodbye-pearl/pearl-ps4.jpg" class="photo" title="Warm comes out of back there. "&gt;&lt;img src="/media/2022-01-goodbye-pearl/pearl-ps4_m.jpg" alt="Pearl straddles a PS4, one back foot atop it while the rest of her is wedged behind it where the fan is"&gt;&lt;/a&gt;
&lt;a href="/media/2022-01-goodbye-pearl/pearl-laundry.jpg" class="photo" title="I probably wasn&amp;#x27;t going to fold this anytime soon anyway. "&gt;&lt;img src="/media/2022-01-goodbye-pearl/pearl-laundry_m.jpg" alt="Pearl&amp;#x27;s head pokes out from within a basketful of laundry"&gt;&lt;/a&gt;
&lt;a href="/media/2022-01-goodbye-pearl/pearl-sunbeam-table.jpg" class="photo" title="Sometimes I wonder how she even finds these. "&gt;&lt;img src="/media/2022-01-goodbye-pearl/pearl-sunbeam-table_m.jpg" alt="Pearl sits on a table looking deeply content as the sun shines directly on her"&gt;&lt;/a&gt;
&lt;a href="/media/2022-01-goodbye-pearl/pearl-watched-outside.jpg" class="photo" title="See if you can find Pearl in this photo. "&gt;&lt;img src="/media/2022-01-goodbye-pearl/pearl-watched-outside_m.jpg" alt="Three cats look out through a sliding glass door; outside, in the distance, Pearl lies on sunny concrete"&gt;&lt;/a&gt;
&lt;a href="/media/2022-01-goodbye-pearl/pearl-backyard-exploring.jpg" class="photo" title="Pearl checks out some wood chips. "&gt;&lt;img src="/media/2022-01-goodbye-pearl/pearl-backyard-exploring_m.jpg" alt="Pearl stands alongside the back fence of the yard, while Ash checks their phone nearby"&gt;&lt;/a&gt;
&lt;a href="/media/2022-01-goodbye-pearl/pearl-backyard-posed.jpg" class="photo" title="Pearl reflects on her next move. "&gt;&lt;img src="/media/2022-01-goodbye-pearl/pearl-backyard-posed_m.jpg" alt="Pearl sits on a scrap of garden fabric or something, bathed in a sunbeam"&gt;&lt;/a&gt;
&lt;/div&gt;&lt;/p&gt;
&lt;p&gt;And I have never known a cat to be quite so comfortable.  Perhaps Anise, on occasion, but he doesn&amp;#8217;t have the raw talent Pearl was born&amp;nbsp;with.&lt;/p&gt;
&lt;p&gt;You could tell she was settling in if she tucked her paws in against her chest, something she always did quite deliberately and distinctly.  But that was only the first stage of comfort.  If you were lucky, she would stretch out one arm really far, perhaps to place her paw on you.  As she dozed off she might lay flat on her side with her limbs outstretched, which meant we always had to check blankets carefully for a flattened Pearl before sitting down.  And if you were really lucky, you might witness Pearl in a chaos configuration, upside-down with her paws&amp;nbsp;wherever.&lt;/p&gt;
&lt;p&gt;But even just sitting up with her eyes closed, she looked so content.  Looking at photos makes me want to take a nap with&amp;nbsp;her.&lt;/p&gt;
&lt;p&gt;&lt;div class="gallery"&gt;
&lt;a href="/media/2022-01-goodbye-pearl/pearl-lounge-arm.jpg" class="photo" title="Wow, Pearl!  Cool arm. "&gt;&lt;img src="/media/2022-01-goodbye-pearl/pearl-lounge-arm_m.jpg" alt="Pearl peeks out of a blanket with one arm outstretched and dangling a comically long distance downwards"&gt;&lt;/a&gt;
&lt;a href="/media/2022-01-goodbye-pearl/pearl-lounge-bed-peek.jpg" class="photo" title="Living her best life. "&gt;&lt;img src="/media/2022-01-goodbye-pearl/pearl-lounge-bed-peek_m.jpg" alt="Pearl&amp;#x27;s arm sticks out from under a blanket on the bed, while a peek of her face is barely visible"&gt;&lt;/a&gt;
&lt;a href="/media/2022-01-goodbye-pearl/pearl-lounge-flat.jpg" class="photo" title="This is Pearl&amp;#x27;s &amp;quot;pork chop&amp;quot; configuration.  If a blanket were over her she&amp;#x27;d basically be invisible. "&gt;&lt;img src="/media/2022-01-goodbye-pearl/pearl-lounge-flat_m.jpg" alt="Pearl sleeps on her side on a blanket, fairly flattened out"&gt;&lt;/a&gt;
&lt;a href="/media/2022-01-goodbye-pearl/pearl-lounge-pillows.jpg" class="photo" title="Occasionally I would wake up with Pearl in my face like this. "&gt;&lt;img src="/media/2022-01-goodbye-pearl/pearl-lounge-pillows_m.jpg" alt="Pearl lays in a funny pose tucked between two pillows, one of which Ash is sleeping on"&gt;&lt;/a&gt;
&lt;a href="/media/2022-01-goodbye-pearl/pearl-content.jpg" class="photo" title="What a face. "&gt;&lt;img src="/media/2022-01-goodbye-pearl/pearl-content_m.jpg" alt="Pearl makes a deeply contented expression"&gt;&lt;/a&gt;
&lt;a href="/media/2022-01-goodbye-pearl/pearl-lounge-flop.jpg" class="photo" title="Cats are liquid. "&gt;&lt;img src="/media/2022-01-goodbye-pearl/pearl-lounge-flop_m.jpg" alt="Pearl sleeps while flopped over someone&amp;#x27;s elbow"&gt;&lt;/a&gt;
&lt;a href="/media/2022-01-goodbye-pearl/pearl-lounge-twist.jpg" class="photo" title="I have never been this comfortable in my whole life. "&gt;&lt;img src="/media/2022-01-goodbye-pearl/pearl-lounge-twist_m.jpg" alt="Pearl&amp;#x27;s head pokes out from under a blanket, upside-down"&gt;&lt;/a&gt;
&lt;a href="/media/2022-01-goodbye-pearl/pearl-lounge-inverted.jpg" class="photo" title="How do you sleep like this?  Also her claw is caught on my shirt. "&gt;&lt;img src="/media/2022-01-goodbye-pearl/pearl-lounge-inverted_m.jpg" alt="Pearl lays asleep on her back, head tucked against her shoulder, one paw outstretched towards the camera"&gt;&lt;/a&gt;
&lt;/div&gt;&lt;/p&gt;
&lt;p&gt;Sadly, Pearl had some health troubles from the start.  She had a kink right at the base of her tail from the day we got her, suggesting it had been injured while at the breeder and not healed right, so she was never able to raise her tail all the way.  She also came home with some sort of intestinal parasite that gave her a lot of&amp;#8230;  um, gastric distress, and while we were able to clear that up quickly, it seemed to recur soon&amp;nbsp;afterwards.&lt;/p&gt;
&lt;p&gt;We took her to the vet again, suspecting more parasites, but multiple tests turned up nothing.  We tried a number of things — different food, sensitive-stomach food, wet food, more water, different treats — but could not seem to figure it out, and so Pearl just had stomachaches on and off for a while.  Sometimes she would sit by the litterbox and grumble, and all I could do was try to reassure&amp;nbsp;her.&lt;/p&gt;
&lt;p&gt;It wasn&amp;#8217;t until a few years later that Ash&amp;#8217;s then-husband, with no explanation whatsoever, spontaneously decided to just feed her some plain chicken mixed with pumpkin purée.  Just like that, she was fine.  I felt like kind of an idiot for not trying that earlier, but after giving her &lt;em&gt;veterinary&lt;/em&gt; sensitive-stomach food and seeing no change, I thought we&amp;#8217;d ruled out food&amp;nbsp;sensitivity.&lt;/p&gt;
&lt;p&gt;We swiftly outlined a general idea of what Pearl could or could not tolerate.  Chicken, pork, pumpkin: &lt;span class="caps"&gt;OK&lt;/span&gt;.  Beef or any kind of organs: she immediately threw up.  Fish: no good.  And yet manufactured food containing only very simple things &lt;em&gt;still&lt;/em&gt; gave her stomachaches, so our best guess was that she &lt;em&gt;also&lt;/em&gt; couldn&amp;#8217;t tolerate fucking xantham gum or something, which is in pretty much all pet food, including the sensitive&amp;nbsp;stuff.&lt;/p&gt;
&lt;p&gt;Regardless, we had a diet she could stomach, so for the rest of her life we made her a custom diet of ground chicken, ground pork belly, pumpkin, and some nutrient powder that didn&amp;#8217;t bother her (which took several attempts to find).  That meant no more free-feeding the other cats, so we got a big dog cage to keep the kibble in, and we&amp;#8217;d let the other cats in there while Pearl was eating her special princess food.  Thus began a multi-year saga during which, &lt;em&gt;every four hours&lt;/em&gt;, like clockwork, Anise would start bothering me to feed&amp;nbsp;him.&lt;/p&gt;
&lt;p&gt;Please do not tell me what I could have done to dissuade Anise or space out the schedule.  I guarantee, he is vastly more dastardly and annoying than you are giving him credit for.  The cats run this household, and I have long since made peace with&amp;nbsp;that.&lt;/p&gt;
&lt;p&gt;The closest to any real insight we got about Pearl was that perhaps her kitten parasites had left her with &lt;span class="caps"&gt;IBS&lt;/span&gt; — a very vague diagnosis of exclusion, and the best anyone could come up with.  But Pearl was happy, so that was good enough.  We eventually found new treats she could stomach,&amp;nbsp;too.&lt;/p&gt;
&lt;p&gt;&lt;div class="gallery"&gt;
&lt;a href="/media/2022-01-goodbye-pearl/pearl-outside-boy-jail.jpg" class="photo" title="Pearl enjoys some scraps, while the other cats look on from within Boy Jail.  Kibble Prison.  The Purrnitentiary. "&gt;&lt;img src="/media/2022-01-goodbye-pearl/pearl-outside-boy-jail_m.jpg" alt="Pearl eats some meat while three cats in a large dog cage stare at her"&gt;&lt;/a&gt;
&lt;a href="/media/2022-01-goodbye-pearl/pearl-cage-sunbeam.jpg" class="photo" title="This looks like a promo ad for a reality show called The Kibble Joint.  Hosted by Gorden Ramsay But He&amp;#x27;s A Cat. "&gt;&lt;img src="/media/2022-01-goodbye-pearl/pearl-cage-sunbeam_m.jpg" alt="Pearl loafs in a sunbeam, right next to the empty cage"&gt;&lt;/a&gt;
&lt;a href="/media/2022-01-goodbye-pearl/pearl-cats-crowding-ash.jpg" class="photo" title="Tiny vultures. "&gt;&lt;img src="/media/2022-01-goodbye-pearl/pearl-cats-crowding-ash_m.jpg" alt="Four cats, one peeking over the rest from behind, crowd around Ash&amp;#x27;s dinner plate and look at it expectantly"&gt;&lt;/a&gt;
&lt;a href="/media/2022-01-goodbye-pearl/pearl-rub-on-chair.jpg" class="photo" title="I don&amp;#x27;t have any more food-related photos of Pearl so please enjoy this one instead. "&gt;&lt;img src="/media/2022-01-goodbye-pearl/pearl-rub-on-chair_m.jpg" alt="Pearl stands against the back of an office chair with a scratchy texture and intensely rubs her chin and cheek against it"&gt;&lt;/a&gt;
&lt;/div&gt;&lt;/p&gt;
&lt;p&gt;Pearl had relatively intense relationships with the other cats, much like she did with&amp;nbsp;people.&lt;/p&gt;
&lt;p&gt;She &lt;em&gt;adored&lt;/em&gt; Napoleon, our furred and largest cat, for some reason.  She often trotted up to him, very eager to sniff him; or when he trotted towards the kibble cage in recent years, she would run alongside him, staring sideways at him.  I don&amp;#8217;t really understand what her feelings were, and Napoleon didn&amp;#8217;t really return them, but he at least tolerated them.  Curiously, I can&amp;#8217;t remember many attempts on Pearl&amp;#8217;s part to snuggle up to Napoleon; she mostly snuggled with the other&amp;nbsp;sphynxes.&lt;/p&gt;
&lt;p&gt;She and Twigs (her uncle, incidentally) spent a ton of time together, and Anise was often in the mix as well.  They&amp;#8217;d often end up in a pile under or within a blanket, or all wedged into the same cat bed, or piled on a chair that had a towel on it.  Sometimes she&amp;#8217;d grumble at Anise for being too much in her personal space, but somehow Twigs&amp;#8217;s presence seemed to defuse everything.  I can&amp;#8217;t remember her ever grumbling at Twigs, in&amp;nbsp;fact.&lt;/p&gt;
&lt;p&gt;Cheeseball is the only cat we have who&amp;#8217;s younger than Pearl.  When he was a kitten, she kind of doted on him like a mom, frequently trying to groom his head.  She kept doing this into his adolescence, even as he was swiftly growing bigger than her, which was endearing and also very&amp;nbsp;funny.&lt;/p&gt;
&lt;p&gt;&lt;div class="gallery"&gt;
&lt;a href="/media/2022-01-goodbye-pearl/pearl-napoleon.jpg" class="photo" title="I don&amp;#x27;t have many photos of Pearl and Napoleon interacting, but this one of kitten Pearl seems to express it well. "&gt;&lt;img src="/media/2022-01-goodbye-pearl/pearl-napoleon_m.jpg" alt="Napoleon and a comically smaller kitten Pearl share a cat bed"&gt;&lt;/a&gt;
&lt;a href="/media/2022-01-goodbye-pearl/pearl-regard-napoleon-1.jpg" class="photo" title="Pearl regards Napoleon longingly from afar. "&gt;&lt;img src="/media/2022-01-goodbye-pearl/pearl-regard-napoleon-1_m.jpg" alt="Pearl stares at Napoleon, who is in my lap"&gt;&lt;/a&gt;
&lt;a href="/media/2022-01-goodbye-pearl/pearl-regard-napoleon-2.jpg" class="photo" title="Napoleon returns his feelings.  I guess.  I don&amp;#x27;t know what he&amp;#x27;s doing here. "&gt;&lt;img src="/media/2022-01-goodbye-pearl/pearl-regard-napoleon-2_m.jpg" alt="Moments later, Napoleon has his outstretched paws resting on Pearl&amp;#x27;s back while she sleeps"&gt;&lt;/a&gt;
&lt;a href="/media/2022-01-goodbye-pearl/pearl-twigs-lick-1.jpg" class="photo" title="Twigs tries to bathe Pearl, who is not having it. "&gt;&lt;img src="/media/2022-01-goodbye-pearl/pearl-twigs-lick-1_m.jpg" alt="Twigs licks a kitten Pearl, who rolls on her back and waves her paws"&gt;&lt;/a&gt;
&lt;a href="/media/2022-01-goodbye-pearl/pearl-twigs-lick-2.jpg" class="photo" title="Pearl makes her feelings expressly clear. "&gt;&lt;img src="/media/2022-01-goodbye-pearl/pearl-twigs-lick-2_m.jpg" alt="Pearl pushes Twigs away with a paw"&gt;&lt;/a&gt;
&lt;a href="/media/2022-01-goodbye-pearl/pearl-and-twigs.jpg" class="photo" title="They became fast friends, once Twigs forgave her betrayal of existing. "&gt;&lt;img src="/media/2022-01-goodbye-pearl/pearl-and-twigs_m.jpg" alt="Pearl and Twigs nestle together comfortably in Ash&amp;#x27;s arms"&gt;&lt;/a&gt;
&lt;a href="/media/2022-01-goodbye-pearl/pearl-under-twigs.jpg" class="photo" title="Twigs always did like sitting on cats, though. "&gt;&lt;img src="/media/2022-01-goodbye-pearl/pearl-under-twigs_m.jpg" alt="Pearl lounges in a cat bed while Twigs props himself up on her"&gt;&lt;/a&gt;
&lt;a href="/media/2022-01-goodbye-pearl/pearl-bite-anise.jpg" class="photo" title="He did nothing to deserve this. "&gt;&lt;img src="/media/2022-01-goodbye-pearl/pearl-bite-anise_m.jpg" alt="Kitten Pearl bits Anise, who is clearly trying to sleep"&gt;&lt;/a&gt;
&lt;a href="/media/2022-01-goodbye-pearl/pearl-twigs-lick-anise.jpg" class="photo" title="Pearl and Twigs both help give Anise a bath. "&gt;&lt;img src="/media/2022-01-goodbye-pearl/pearl-twigs-lick-anise_m.jpg" alt="Three sphynxes lay wrapped in a blanket, Pearl and Twigs both licking the ears of Anise in the middle"&gt;&lt;/a&gt;
&lt;a href="/media/2022-01-goodbye-pearl/pearl-trio.jpg" class="photo" title="The three of them often snuggled up somewhere together. "&gt;&lt;img src="/media/2022-01-goodbye-pearl/pearl-trio_m.jpg" alt="Three sphynxes lay in a pile under a blanket or two"&gt;&lt;/a&gt;
&lt;a href="/media/2022-01-goodbye-pearl/pearl-anise-sunbeam-1.jpg" class="photo" title="Pearl does not want to share this sunbeam with Anise. "&gt;&lt;img src="/media/2022-01-goodbye-pearl/pearl-anise-sunbeam-1_m.jpg" alt="Pearl lays in a sunbeam with Twigs nearby; Anise has tried to approach and she is pushing him away with a sour expression"&gt;&lt;/a&gt;
&lt;a href="/media/2022-01-goodbye-pearl/pearl-anise-sunbeam-3.jpg" class="photo" title="Twigs sacrifices his peace and quiet for the greater good. "&gt;&lt;img src="/media/2022-01-goodbye-pearl/pearl-anise-sunbeam-3_m.jpg" alt="Pearl&amp;#x27;s paws are now on Twigs&amp;#x27;s face, though he doesn&amp;#x27;t seem bothered at all"&gt;&lt;/a&gt;
&lt;a href="/media/2022-01-goodbye-pearl/pearl-anise-sunbeam-4.jpg" class="photo" title="The system reaches equilibrium. "&gt;&lt;img src="/media/2022-01-goodbye-pearl/pearl-anise-sunbeam-4_m.jpg" alt="Pearl remains in the sunbeam and licks one paw, while Anise has settled a short distance away, facing the other way"&gt;&lt;/a&gt;
&lt;a href="/media/2022-01-goodbye-pearl/pearl-lick-cheeseball.jpg" class="photo" title="This very large child is quite dirty. "&gt;&lt;img src="/media/2022-01-goodbye-pearl/pearl-lick-cheeseball_m.jpg" alt="Pearl licks kitten Cheeseball&amp;#x27;s head, though he&amp;#x27;s already bigger than her"&gt;&lt;/a&gt;
&lt;/div&gt;&lt;/p&gt;
&lt;p&gt;We moved in 2018, and spent the summer with a former acquaintance&amp;#8217;s parents, as they had a finished and furnished basement that was practically an apartment all on its own.  Unfortunately, they had four cats of their own, for a total of &lt;em&gt;nine&lt;/em&gt; crammed into a relatively small space.  (One of the parents couldn&amp;#8217;t be around cat hair in the medium term, due to&amp;nbsp;reasons.)&lt;/p&gt;
&lt;p&gt;One of the cats, Seamus, was a maine coon, and by all accounts kind of an asshole.  He made a habit out of chasing Napoleon around, which Napoleon did not like at all, and which would result in Pearl chasing &lt;em&gt;him&lt;/em&gt; to defend Napoleon, and then Anise chasing after Pearl because everyone is running around and he doesn&amp;#8217;t quite understand why but he doesn&amp;#8217;t want to be left out.  We kept the cats separated as best we could, but we didn&amp;#8217;t have much space to work with, and we were already trying to sequester Cheeseball, who we&amp;#8217;d just adopted as a kitten.  Everything was just kind of a&amp;nbsp;mess.&lt;/p&gt;
&lt;p&gt;Anyway this kinda stressed everyone&amp;nbsp;out.&lt;/p&gt;
&lt;p&gt;I bring it up because of one particular event.  The only segregated parts of the basement were the bathroom and a somewhat awkwardly-shaped bedroom.  The bedroom was &lt;em&gt;exclusively&lt;/em&gt; for our cats.  I don&amp;#8217;t remember exactly what led up to this, but at some point Seamus made a beeline for the bedroom while Pearl was just inside the open door.  I&amp;#8217;m guessing Napoleon was in there&amp;nbsp;too.&lt;/p&gt;
&lt;p&gt;Pearl was &lt;strong&gt;absolutely&lt;/strong&gt; not having this.  She stood her ground and hissed hard enough to stop this absolutely massive cat in his tracks.  She was so mad that she &lt;em&gt;peed on the floor&lt;/em&gt; (which was, thankfully, vinyl).  We got there to intervene about half a second later, but wow!  She drew a line in the sand and under no circumstances was this bully going to cross it.  We have always looked back fondly upon this &amp;#8220;rage piss&amp;#8221;&amp;nbsp;incident.&lt;/p&gt;
&lt;p&gt;I think Pearl was left a little rattled, though.  Even at the time, she growled at the other maine coon there, who was an absolute sweetheart and rarely did more than sit nicely and ask to be pet.  Once we were out of there, she seemed a little distrusting of Anise, often growling at him or biting his haunch merely for sitting nearby (which would entice a bewildered Anise into smacking her, justifying her reaction).  I wish we hadn&amp;#8217;t stayed&amp;nbsp;there.&lt;/p&gt;
&lt;p&gt;Cheeseball was also growing up and wanted to play with Pearl, because playing is how he engages with pretty much everything; alas, he was a bit too rowdy for Pearl.  Twigs, infinitely patient, was there to absorb a lot of&amp;nbsp;this.&lt;/p&gt;
&lt;p&gt;But then &lt;a href="https://eev.ee/blog/2019/10/26/goodbye-twigs/"&gt;Twigs died&lt;/a&gt;, and the cats&amp;#8217; relationships seemed to deteriorate.  Cheeseball liked Pearl, but he always wanted to fight with her, which she didn&amp;#8217;t like.  Anise liked Pearl, but she seemed to resent him a lot of the time, and there was no Twigs to separate them.  Pearl liked Napoleon, but Napoleon liked to be by&amp;nbsp;himself.&lt;/p&gt;
&lt;p&gt;It was &lt;em&gt;okay&lt;/em&gt;, but&amp;nbsp;tense.&lt;/p&gt;
&lt;p&gt;Maybe I&amp;#8217;m overstating this.  Going back through photos of Pearl, I&amp;#8217;ve found plenty from the post-Twigs era where she&amp;#8217;s still hanging out with Anise peacefully.  A number of their conflicts even started because &lt;em&gt;she&lt;/em&gt; would approach Anise to sit by him, &lt;em&gt;then&lt;/em&gt; growl at him.  No wonder he was confused.  Sometimes she would groom him and start growling, &lt;em&gt;while licking his ear&lt;/em&gt;.  Hello?  What are you doing??  What do you want from him&amp;nbsp;here.&lt;/p&gt;
&lt;p&gt;Still, that must mean she still liked him.  She just had some complicated feelings.  It always made me a little sad when they couldn&amp;#8217;t get along, though.  I&amp;#8217;d gotten Anise in the first place in part to give Twigs a friend, and Pearl and Twigs had always gotten along well, and now&amp;#8230;&amp;nbsp;well.&lt;/p&gt;
&lt;p&gt;&lt;div class="gallery"&gt;
&lt;a href="/media/2022-01-goodbye-pearl/pearl-under-anise.jpg" class="photo" title="She&amp;#x27;d never let him get away with that if she didn&amp;#x27;t like him. "&gt;&lt;img src="/media/2022-01-goodbye-pearl/pearl-under-anise_m.jpg" alt="Pearl under a blanket, with Anise stretched out and resting his paws on top of her"&gt;&lt;/a&gt;
&lt;a href="/media/2022-01-goodbye-pearl/pearl-anise-cat-bed.jpg" class="photo" title="I do wonder which one was here first. "&gt;&lt;img src="/media/2022-01-goodbye-pearl/pearl-anise-cat-bed_m.jpg" alt="Pearl and Anise share a cat bed"&gt;&lt;/a&gt;
&lt;a href="/media/2022-01-goodbye-pearl/pearl-anise-vent.jpg" class="photo" title="There&amp;#x27;s a heater vent buried somewhere under there. "&gt;&lt;img src="/media/2022-01-goodbye-pearl/pearl-anise-vent_m.jpg" alt="Pearl and Anise lay in a row, wedged between some boxes and the wall"&gt;&lt;/a&gt;
&lt;a href="/media/2022-01-goodbye-pearl/pearl-anise-together.jpg" class="photo" title="Best of friends.  For now. "&gt;&lt;img src="/media/2022-01-goodbye-pearl/pearl-anise-together_m.jpg" alt="Pearl and Anise nap under a blanket, facing each other, with their arms kind of wrapped around each other"&gt;&lt;/a&gt;
&lt;a href="/media/2022-01-goodbye-pearl/pearl-groomed-by-napoleon.jpg" class="photo" title="I guess he did like her back. "&gt;&lt;img src="/media/2022-01-goodbye-pearl/pearl-groomed-by-napoleon_m.jpg" alt="Pearl tilts her head down while Napoleon licks the top of it"&gt;&lt;/a&gt;
&lt;a href="/media/2022-01-goodbye-pearl/pearl-medley-on-ash.jpg" class="photo" title="This is just cat life. "&gt;&lt;img src="/media/2022-01-goodbye-pearl/pearl-medley-on-ash_m.jpg" alt="Pearl, Anise, and Napoleon are all piled on top of Ash in bed"&gt;&lt;/a&gt;
&lt;/div&gt;&lt;/p&gt;
&lt;p&gt;Having said all this about how great and lovely Pearl is, her presumptuousness also made her a &lt;em&gt;huge pest&lt;/em&gt; in some very specific ways.  For example, once we&amp;#8217;d settled into the food routine that saved her from constant stomachaces, one of her favorite things to do was to go over to the kibble cage and try to find kibble that had escaped from it.  If she could get away with it, she would stick her paw between the bars and pull kibble (or the entire bowl) out to&amp;nbsp;eat.&lt;/p&gt;
&lt;p&gt;It was slightly annoying, and also very funny.  We called this pulling a heist.  And then she&amp;#8217;d have awful gas some hours&amp;nbsp;later.&lt;/p&gt;
&lt;p&gt;I also very distinctly remembering getting takeout one time, which happened to include a breaded and fried slab of fish.  I had the little takeout container on the table in front of me, and I think I was fiddling with the wrapper on their plastic fork or something, when Pearl came along, sniffed it&amp;#8230;  and then &lt;em&gt;bit the fish&lt;/em&gt; and &lt;em&gt;pulled the whole filet out of the container&lt;/em&gt;.  Right in front of me!  Points for boldness, I guess.  She wasn&amp;#8217;t &lt;em&gt;quite&lt;/em&gt; so audacious any other time, but she must&amp;#8217;ve really liked the smell of that&amp;nbsp;fish.&lt;/p&gt;
&lt;p&gt;And while she was generally pretty picky about what she would consider a toy, she did, on occasion, like to bite the arms of my glasses.  Once I was laying next to her and petting her while she purred, and she stuck a paw in between my glasses and my face, &lt;em&gt;pulled them off&lt;/em&gt;, and tried to bite them — purring all the&amp;nbsp;while.&lt;/p&gt;
&lt;p&gt;&lt;div class="gallery"&gt;
&lt;a href="/media/2022-01-goodbye-pearl/pearl-glasses.jpg" class="photo" title="Admittedly, I&amp;#x27;m enabling her behavior. "&gt;&lt;img src="/media/2022-01-goodbye-pearl/pearl-glasses_m.jpg" alt="Pearl takes a swipe at my glasses, which I am clearly offering to her"&gt;&lt;/a&gt;
&lt;a href="/media/2022-01-goodbye-pearl/pearl-inspect-steak.jpg" class="photo" title="Twigs waits patiently to see if he will receive scraps.  Anise peeks from behind.  Pearl shoves her whole face in there. "&gt;&lt;img src="/media/2022-01-goodbye-pearl/pearl-inspect-steak_m.jpg" alt="Three cats lurk, at various distances, around Ash, who is sitting on the couch and attempting to eat dinner"&gt;&lt;/a&gt;
&lt;a href="/media/2022-01-goodbye-pearl/pearl-lollipop.jpg" class="photo" title="Pearl would like to investigate this, please, thank you, and nothing you do will stop her. "&gt;&lt;img src="/media/2022-01-goodbye-pearl/pearl-lollipop_m.jpg" alt="Pearl straddling Ash&amp;#x27;s shoulder and outstretched hand to sniff a lollipop that Ash is trying to keep out of reach"&gt;&lt;/a&gt;
&lt;/div&gt;&lt;/p&gt;
&lt;p&gt;My favorite Pearl trick was what we dubbed &amp;#8220;mouse alert&amp;#8221;.  If Pearl was looking for someone — often anyone at all, but sometimes a particular person who was absent or in a room with a closed door — she would find one of her toy mice and carry it around doing a very loud, muffled meow.  If she saw you she would then drop the mouse and trot over, making happy high-pitched meows&amp;nbsp;instead.&lt;/p&gt;
&lt;p&gt;Sometimes she&amp;#8217;d start out with regular meows, which we could hear from the other side of the house, but then they&amp;#8217;d abruptly turn deeper and longer, and we knew she&amp;#8217;d picked a mouse up.  It was so charming and &lt;em&gt;so&lt;/em&gt; funny.  Every so often we&amp;#8217;d find a pile of mice outside a door and we knew that Pearl had been trying to open it.  She later expanded her roster to include Big Mouse — a plush almost half her size who became her favorite — and a plush of a single &lt;span class="caps"&gt;HIV&lt;/span&gt; virus that she must&amp;#8217;ve stolen from my&amp;nbsp;desk.&lt;/p&gt;
&lt;p&gt;She didn&amp;#8217;t play with the mice, either.  I have video of her playing with &lt;em&gt;a&lt;/em&gt; mouse when she was fairly young, but it&amp;#8217;s not one of the mice we have now.  She seemed to regard them as precious, her comforting belongings that she could almost always lure us out of hiding with.  &amp;#8220;Come look at my mouse!&amp;#8221;  Sometimes she&amp;#8217;d carry them around quietly, just to have one or two nearby in a comfortable&amp;nbsp;spot.&lt;/p&gt;
&lt;p&gt;I tried for her &lt;em&gt;whole life&lt;/em&gt; to get a recording of this, which proved nearly impossible, because she&amp;#8217;d stop if she knew anyone was nearby!  I got a clear recording only once, a week before she died; I was in our dark bedroom, filming into Ash&amp;#8217;s office, and I don&amp;#8217;t think she realized I was there.  There&amp;#8217;s a link at the&amp;nbsp;bottom.&lt;/p&gt;
&lt;p&gt;Her other favorite possession was string.  Pearl &lt;em&gt;loved&lt;/em&gt; to play string.  She would ask for it by name.  No, really.  If she wanted to play string, she would find (or bring) a string and sit on it hoping someone noticed, and if that didn&amp;#8217;t work, I&amp;#8217;m pretty sure she had a specific meow for asking you to please follow her to string and then play with&amp;nbsp;it.&lt;/p&gt;
&lt;p&gt;Playing string with her was a slightly frustrating affair, but perhaps I just didn&amp;#8217;t understand the rules.  They &lt;em&gt;seemed&lt;/em&gt; to be: I should wiggle the string; then Pearl grabs the string; then Pearl keeps the string.  That doesn&amp;#8217;t end the game, though.  I should keep trying, in vain, to get the string back, while Pearl simply keeps&amp;nbsp;winning.&lt;/p&gt;
&lt;p&gt;A great thing to do was dangle it above her, at which point she&amp;#8217;d stand up to try to get it and chomp at it, audibly.  I loved her little chomp sound.  I can&amp;#8217;t even do it myself; I feel like I&amp;#8217;d hurt my&amp;nbsp;teeth.&lt;/p&gt;
&lt;p&gt;After she was through adolescence, string was the only thing she really wanted to play with.  She might&amp;#8217;ve chased a laser pointer a couple of times, but string was the one thing she would ask for.  Occasionally I&amp;#8217;d try to play with Anise with a string, but Pearl had a fucking sixth sense for when string was happening, and she would appear from nowhere and go absolutely nuts over it while Anise sat back and&amp;nbsp;watched.&lt;/p&gt;
&lt;p&gt;&lt;div class="gallery"&gt;
&lt;a href="/media/2022-01-goodbye-pearl/pearl-string-please-1.jpg" class="photo" title="Please.  Please may I have some string. "&gt;&lt;img src="/media/2022-01-goodbye-pearl/pearl-string-please-1_m.jpg" alt="Pearl looks longingly at the camera while hunkered down atop a length of twine"&gt;&lt;/a&gt;
&lt;a href="/media/2022-01-goodbye-pearl/pearl-string-please-2.jpg" class="photo" title="I would love any spare string you have. "&gt;&lt;img src="/media/2022-01-goodbye-pearl/pearl-string-please-2_m.jpg" alt="Pearl looks longingly at the camera, again, with a length of string dangling from a dresser in the background"&gt;&lt;/a&gt;
&lt;a href="/media/2022-01-goodbye-pearl/pearl-with-mouse.jpg" class="photo" title="She definitely put that mouse there herself.  Also, I think this box was on top of a heater vent. "&gt;&lt;img src="/media/2022-01-goodbye-pearl/pearl-with-mouse_m.jpg" alt="Pearl sits in a box with a small plush mouse next to her"&gt;&lt;/a&gt;
&lt;a href="/media/2022-01-goodbye-pearl/pearl-swipe-string.jpg" class="photo" title="My shutter speed is not high enough for this. "&gt;&lt;img src="/media/2022-01-goodbye-pearl/pearl-swipe-string_m.jpg" alt="Pearl swipes, blurrily, at a string dangling in front of her"&gt;&lt;/a&gt;
&lt;/div&gt;&lt;/p&gt;
&lt;p&gt;In March 2021, I took Pearl to an &lt;span class="caps"&gt;ER&lt;/span&gt; vet over very rapid breathing.  They told me she&amp;#8217;d had fluid in her lungs and diagnosed her with congestive heart failure.  That&amp;#8217;s when your heart can&amp;#8217;t pump hard enough; part of Pearl&amp;#8217;s heart wall had thinned and weakened, and one chamber was enlarged.  She had to be hospitalized overnight.  I drove home thinking I&amp;#8217;d never see her&amp;nbsp;again.&lt;/p&gt;
&lt;p&gt;They couldn&amp;#8217;t identify a cause.  She was given a prognosis of &amp;#8220;not fantastic&amp;#8221; and prescribed a growing mountain of medication, which Ash dutifully gave to her every twelve hours for months on end, even when Pearl refused it.  Sometimes Pearl had to be bribed with treats in order to eat at all, though I later traced that to a batch of food with insufficient pumpkin for her&amp;nbsp;liking.&lt;/p&gt;
&lt;p&gt;We had to keep her stress level low, which meant keeping her completely separated from the other cats (or at the very least Cheeseball) as much as possible.  That meant Ash vanished into a closed room for most of every day to work while keeping an eye on Pearl — who was, after all, Ash&amp;#8217;s cat.  That also left me with three other cats constantly vying for my&amp;nbsp;attention.&lt;/p&gt;
&lt;p&gt;For several months we often couldn&amp;#8217;t even sleep in the same room — Pearl and Anise couldn&amp;#8217;t be left together, and Anise makes a racket all night if he&amp;#8217;s shut out.  Early on, our roommate would often take Pearl overnight (even despite being allergic to cats), but as time went on, Ash felt a stronger impulse to be around her as much as possible.  Eventually we found we could have both Anise and Pearl overnight as long as we put a sweater on Anise and had sufficient extra blankets on the bed, but honestly it felt like a constant logistical&amp;nbsp;nightmare.&lt;/p&gt;
&lt;p&gt;Even with all this, we still had several more &lt;span class="caps"&gt;ER&lt;/span&gt; visits, several more&amp;nbsp;hospitalizations.&lt;/p&gt;
&lt;p&gt;Still, Pearl seemed to be doing okay.  She was happy, she engaged with us, she purred, she snuggled, she nuzzled, she played.  She was fine, and stable, until she&amp;nbsp;wasn&amp;#8217;t.&lt;/p&gt;
&lt;p&gt;It was January 11, and it was the first &lt;span class="caps"&gt;ER&lt;/span&gt; visit for rapid breathing in a while.  We handed her over, they hospitalized her, and we left, assuming we&amp;#8217;d pick her up in the morning and she&amp;#8217;d be fine, as had always&amp;nbsp;happened.&lt;/p&gt;
&lt;p&gt;We weren&amp;#8217;t home for long before they called us.  Pearl wasn&amp;#8217;t recovering this time, and wouldn&amp;#8217;t make it through the&amp;nbsp;night.&lt;/p&gt;
&lt;p&gt;We raced back.  We saw Pearl, struggling to breathe, even on oxygen.  We pet her and told her it would be okay.  She cried out for help.  Ash held&amp;nbsp;her.&lt;/p&gt;
&lt;p&gt;And then we let her&amp;nbsp;go.&lt;/p&gt;
&lt;p&gt;&lt;div class="gallery"&gt;
&lt;a href="/media/2022-01-goodbye-pearl/pearl-gone-mouse.jpg" class="photo" title="Pearl&amp;#x27;s mouse, right where she left it. "&gt;&lt;img src="/media/2022-01-goodbye-pearl/pearl-gone-mouse_m.jpg" alt="A toy mouse lays atop a heating vent"&gt;&lt;/a&gt;
&lt;a href="/media/2022-01-goodbye-pearl/pearl-gone-string.jpg" class="photo" title="Pearl&amp;#x27;s string, right where she left it. "&gt;&lt;img src="/media/2022-01-goodbye-pearl/pearl-gone-string_m.jpg" alt="A bundle of string lays on the floor amidst some laundry"&gt;&lt;/a&gt;
&lt;a href="/media/2022-01-goodbye-pearl/pearl-gone-corner.jpg" class="photo" title="Pearl&amp;#x27;s bathroom corner, waiting for her.  I tidied it up a bit for her.  You can see Big Mouse and Twigs&amp;#x27;s tiny mouse. "&gt;&lt;img src="/media/2022-01-goodbye-pearl/pearl-gone-corner_m.jpg" alt="A nook surrounded by windows, with a towel in one corner and several plush toys neatly arranged on it"&gt;&lt;/a&gt;
&lt;/div&gt;&lt;/p&gt;
&lt;p&gt;I love and miss so many little things.  She had such beautiful eyes, like Twigs did, though she squinted a lot so it always felt like a special treat when I could see them clearly.  Her whole face scrunched when she meowed.  She had a marble pattern, so I guess she would&amp;#8217;ve been a calico.  I didn&amp;#8217;t even notice it when we first got her, and then one day it jumped right out at me and I felt briefly like our kitten had been replaced with a different one.  She had a funny little clump of four hairs that stuck out from her hip.  She had marbling on her pawpads,&amp;nbsp;too.&lt;/p&gt;
&lt;p&gt;I love her wide vocabulary of very cute little meows, in contrast with Twigs&amp;#8217;s more raucous ones.  She reserved them for special occasions, opting to chirr most of the&amp;nbsp;time.&lt;/p&gt;
&lt;p&gt;I love how, when she was surprised by something, she would simply jump straight up in the air an inch, then come down.  No other movement.  It was like she was tweened.  I never tried to spook her on purpose to see this, but she was a little prone to being&amp;nbsp;spooked.&lt;/p&gt;
&lt;p&gt;I love how, when she&amp;#8217;d knead at a soft blanket, she did just a few quick little motions and then she was done.  It was so dainty.  I always called it kitty paws, to distinguish from cat&amp;nbsp;paws.&lt;/p&gt;
&lt;p&gt;I love how she&amp;#8217;d do a straight upwards stretch that somehow made her ears flick inside out&amp;nbsp;briefly.&lt;/p&gt;
&lt;p&gt;I love the very deliberate way she tucked her paws, and how she would gently hold onto someone&amp;#8217;s shoulders while getting a taxi ride.  Everything she did came across as so&amp;nbsp;purposeful.&lt;/p&gt;
&lt;p&gt;I love how Ash had found that rubbing their face on Pearl&amp;#8217;s side as a kitten would get her to purr, and that kept working for her whole life, and it&amp;#8217;s basically what she ended up doing to people in&amp;nbsp;return.&lt;/p&gt;
&lt;p&gt;I love how she had a funny obsession with water.  I can&amp;#8217;t really explain it, and I don&amp;#8217;t know what she found so interesting.  If I took a swig from my water bottle with Pearl nearby, she would climb on whatever was necessary to sniff at the nozzle.  If I opened a soda with Pearl nearby, she&amp;#8217;d stick her nose right in the opening, then recoil when the bubbles fizzed her.  She didn&amp;#8217;t enjoy baths or anything, she just liked&amp;#8230;  water.  From afar.  Like with Napoleon,&amp;nbsp;perhaps.&lt;/p&gt;
&lt;p&gt;I love how she nuzzled so hard that she hit maximum nuzzle, and so she would also sort of gently swipe the air with her paw as well, for extra nuzzling&amp;nbsp;power.&lt;/p&gt;
&lt;p&gt;I love her funny &amp;#8220;bug off&amp;#8221; sweater, illustrated with a ladybug, which seemed to capture her personality well: don&amp;#8217;t be rude to me, but expressed in a very cute&amp;nbsp;manner.&lt;/p&gt;
&lt;p&gt;I love how she adopted the sort of extended windowsill in our bathroom as her own, and would lay there on sunny days and roll around on a&amp;nbsp;towel.&lt;/p&gt;
&lt;p&gt;I love that she was pampered right to the end.  Over the course of recent weeks, Ash would keep giving me updates on Pearl&amp;#8217;s development of a new routine, where she would sit in a Treat Spot she had designated, possibly meow once or twice, and wait very nicely until Ash gave her a treat.  And then Ash would eventually capitulate, helpful before the polite ministrations of this very tiny cat, and give her a treat.  It seemed that the number of treats Pearl was managing to get per day was gradually increasing, and so I asked every time: why not simply not give her a treat?  But I knew the&amp;nbsp;answer.&lt;/p&gt;
&lt;p&gt;If you cried, there were decent odds that Pearl would come and comfort you, come chirp at you and nuzzle until you felt&amp;nbsp;better.&lt;/p&gt;
&lt;p&gt;When we first moved here, Ash&amp;#8217;s ex-husband had driven the truck containing all our stuff, and he slept here one night before leaving for good.  The day after he&amp;#8217;d left, we heard Pearl doing mouse alert in the room he&amp;#8217;d slept in, and I just broke down sobbing at the kitchen table, thinking about how Pearl liked him despite everything and was just trying to find him, and we had no way to tell her he wasn&amp;#8217;t coming back or explain any of it to her.  To her, one of her favorite people had just disappeared, and that was so&amp;nbsp;sad.&lt;/p&gt;
&lt;p&gt;But Pearl heard me, came over, jumped on the kitchen table, and purred and headbutted me like crazy.  The idea that I was sad for her and she still wanted to comfort &lt;em&gt;me&lt;/em&gt; made me cry&amp;nbsp;harder.&lt;/p&gt;
&lt;p&gt;She would also headbutt and nuzzle Ash specifically on the mouth when they sang, or do the same to me if I whistled competently.  I suppose she liked music, but only from&amp;nbsp;us.&lt;/p&gt;
&lt;p&gt;Most of all, I love&amp;#8230;  how much she doted on Ash.  She slept alongside them (me only a few times), she followed them around, she waited outside doors for them.  They were her favorite person.  I feel so bad for them, to have lost both Twigs and Pearl back to&amp;nbsp;back.&lt;/p&gt;
&lt;p&gt;&lt;div class="gallery"&gt;
&lt;a href="/media/2022-01-goodbye-pearl/pearl-water-1.jpg" class="photo" title="Sometimes she would climb all over me just to do this. "&gt;&lt;img src="/media/2022-01-goodbye-pearl/pearl-water-1_m.jpg" alt="Pearl sniffs suspiciously at the nozzle of a reusable water bottle"&gt;&lt;/a&gt;
&lt;a href="/media/2022-01-goodbye-pearl/pearl-water-2.jpg" class="photo" title="I wonder what she&amp;#x27;s thinking. "&gt;&lt;img src="/media/2022-01-goodbye-pearl/pearl-water-2_m.jpg" alt="Pearl stares down into a half-full bottled water"&gt;&lt;/a&gt;
&lt;a href="/media/2022-01-goodbye-pearl/pearl-wait-outside-door.jpg" class="photo" title="Guess who&amp;#x27;s in the bathroom. "&gt;&lt;img src="/media/2022-01-goodbye-pearl/pearl-wait-outside-door_m.jpg" alt="Pearl sits patiently outside the bathroom door"&gt;&lt;/a&gt;
&lt;a href="/media/2022-01-goodbye-pearl/pearl-bug-off.jpg" class="photo" title="Pearl&amp;#x27;s favorite shirt. "&gt;&lt;img src="/media/2022-01-goodbye-pearl/pearl-bug-off_m.jpg" alt="Pearl seen from above to be wearing a green and white striped shirt with &amp;quot;BUG OFF!&amp;quot; and a ladybug emblem"&gt;&lt;/a&gt;
&lt;/div&gt;&lt;/p&gt;
&lt;p&gt;It&amp;#8217;s been&amp;#8230;  two weeks now.  Just over, because it took me another day to finish this&amp;nbsp;post.&lt;/p&gt;
&lt;p&gt;I don&amp;#8217;t know if it&amp;#8217;s fully clicked yet.  I didn&amp;#8217;t see Pearl much during the day, since she&amp;#8217;d be tucked away in Ash&amp;#8217;s office slash our bedroom.  I saw her mostly at night and first thing in the morning.  So while I&amp;#8217;m out here, at my desk, it&amp;#8217;s like nothing has changed.  It only sinks in when I go upstairs and see the door left open, see a bed with no Pearl tucked in it&amp;nbsp;somewhere.&lt;/p&gt;
&lt;p&gt;It&amp;#8217;s kind of dumbfounding just how much of this house and our lives had warped around Pearl, around this one tiny cat who loved everyone.  So many things have disappeared or seem superfluous now.  I was already free-feeding the other cats again since Pearl wasn&amp;#8217;t allowed to roam the house unsupervised, but now we don&amp;#8217;t need the kibble cage at all.  Half our doors had been kept closed to make a few different places for Pearl to stay, but now none of that is necessary.  Litterboxes had ended up scattered throughout the house so Pearl would always have access to one; now they&amp;#8217;re back to being in a few central&amp;nbsp;locations.&lt;/p&gt;
&lt;p&gt;Ash doesn&amp;#8217;t have to wake up at a specific time every day to give Pearl medicine.  Pearl won&amp;#8217;t wake us up to feed her.  We don&amp;#8217;t have to make her food, ever&amp;nbsp;again.&lt;/p&gt;
&lt;p&gt;And there are so many things that were only for Pearl.  This wasn&amp;#8217;t the case for anyone else.  &lt;a href="https://eev.ee/blog/2013/04/30/goodbye-styx/"&gt;Styx&lt;/a&gt; only had communal cat sweaters; his favorite toy was loose change on my desk.  Twigs, too, only had sweaters that Anise and Pearl inherited; his one dedicated toy was a single very tiny mouse he sometimes played&amp;nbsp;with.&lt;/p&gt;
&lt;p&gt;But Pearl?  Half the sweaters we have &lt;em&gt;only&lt;/em&gt; fit Pearl.  Her mice were very much hers.  Even her string was very much hers.  We have a mortar and pestle that were specifically for grinding up her medication, oral syringes only Pearl used.  She had possessions of her very own, things she&amp;#8217;s left&amp;nbsp;behind.&lt;/p&gt;
&lt;p&gt;We knew this was coming, of course.  Without the intervention of modern medicine, she would have died last March, and the outlook for heart failure in a cat isn&amp;#8217;t great.  I&amp;#8217;ve already grieved for her several times over the past year.  I didn&amp;#8217;t see her much during the summer, but I&amp;#8217;d been trying to spend more deliberate time with her in recent months, and I&amp;#8217;m glad I did.  I regret nothing.  I earned her purrs, I played string with her exactly the right amount, I woke up to her stealing my pillow.  I got the full Pearl&amp;nbsp;experience.&lt;/p&gt;
&lt;p&gt;And so did she.  Ash took her outside extra over the summer, let her see a bit of the outside world (even if it was only our yard).  We let her roam the house when we could, banishing Cheeseball to a room by himself if necessary, though she usually ended up sitting on a vent or my lap (or trying to heist some kibble).  She got lots of treats, lots of love, lots of blankets, and even a vent all to herself.  What more could she ask&amp;nbsp;for?&lt;/p&gt;
&lt;p&gt;She was living on borrowed time, but we borrowed every second we could.  I don&amp;#8217;t know what else we could&amp;#8217;ve done.  And we were there for her right up until the end.  We didn&amp;#8217;t have that opportunity with Twigs; he died in the back room, surrounded by&amp;nbsp;strangers.&lt;/p&gt;
&lt;p&gt;In the end, her heart was literally too&amp;nbsp;big.&lt;/p&gt;
&lt;p&gt;&lt;div class="gallery"&gt;
&lt;a href="/media/2022-01-goodbye-pearl/pearl-judging.jpg" class="photo" title="I feel like she&amp;#x27;s judging me.  Or maybe judging my shoes. "&gt;&lt;img src="/media/2022-01-goodbye-pearl/pearl-judging_m.jpg" alt="Pearl sits, paws folded, looking downwards with a frumpy frowny expression"&gt;&lt;/a&gt;
&lt;a href="/media/2022-01-goodbye-pearl/pearl-stretch-on-ash.jpg" class="photo" title="Don&amp;#x27;t mind me. "&gt;&lt;img src="/media/2022-01-goodbye-pearl/pearl-stretch-on-ash_m.jpg" alt="Pearl does a big stretch, straight upwards, while standing on Ash&amp;#x27;s chest, as they try to look at their phone"&gt;&lt;/a&gt;
&lt;a href="/media/2022-01-goodbye-pearl/pearl-anise-tv.jpg" class="photo" title="Pearl and Anise catch the latest episode of their favorite drama, about a squirrel, who may or may not get got. "&gt;&lt;img src="/media/2022-01-goodbye-pearl/pearl-anise-tv_m.jpg" alt="Pearl and Anise stare at a video of a squirrel on the TV"&gt;&lt;/a&gt;
&lt;a href="/media/2022-01-goodbye-pearl/pearl-standing.jpg" class="photo" title="This looks so fucking weird to me every time I see it?? "&gt;&lt;img src="/media/2022-01-goodbye-pearl/pearl-standing_m.jpg" alt="Pearl stands up on her hind legs, making her shoulders look very strange"&gt;&lt;/a&gt;
&lt;a href="/media/2022-01-goodbye-pearl/pearl-from-below.jpg" class="photo" title="Beautiful. "&gt;&lt;img src="/media/2022-01-goodbye-pearl/pearl-from-below_m.jpg" alt="Pearl&amp;#x27;s face as seen from a low angle"&gt;&lt;/a&gt;
&lt;a href="/media/2022-01-goodbye-pearl/pearl-meow.jpg" class="photo" title="I caught her in mid-meow here, which is just miraculous timing. "&gt;&lt;img src="/media/2022-01-goodbye-pearl/pearl-meow_m.jpg" alt="Pearl&amp;#x27;s face is scrunched as she meows"&gt;&lt;/a&gt;
&lt;a href="/media/2022-01-goodbye-pearl/pearl-eevee-cushion.jpg" class="photo" title="That&amp;#x27;s my Eevee cushion.  My favorite genre is Pearl settled on things that look like you extruded Pearl&amp;#x27;s shadow outwards. "&gt;&lt;img src="/media/2022-01-goodbye-pearl/pearl-eevee-cushion_m.jpg" alt="Pearl sits with tucked paws on a little pillow that&amp;#x27;s only a little bigger than she is"&gt;&lt;/a&gt;
&lt;a href="/media/2022-01-goodbye-pearl/pearl-ash-shoulder.jpg" class="photo" title="Is she sleeping?  How does she do that? "&gt;&lt;img src="/media/2022-01-goodbye-pearl/pearl-ash-shoulder_m.jpg" alt="Pearl appears to nap, laying across Ash&amp;#x27;s shoulders while they draw"&gt;&lt;/a&gt;
&lt;/div&gt;&lt;/p&gt;
&lt;p&gt;This&amp;nbsp;sucks.&lt;/p&gt;
&lt;p&gt;Pearl deserved better.  She was dealt a bad hand from the beginning, but she was still friendly and kind, and then this happened.  She was so young, too — her eighth birthday would&amp;#8217;ve been next month.  She, like Twigs, should&amp;#8217;ve had twice as&amp;nbsp;long.&lt;/p&gt;
&lt;p&gt;Things won&amp;#8217;t be difficult for her any more, I guess.  I don&amp;#8217;t know how much that comforts&amp;nbsp;me.&lt;/p&gt;
&lt;p&gt;Everything else moves on.  Pearl continued until the night of January 11, 2022, but can go no further.  We&amp;#8217;re forced to leave her there, retaining only memories, while time carries us gently forward, ever further&amp;nbsp;away.&lt;/p&gt;
&lt;p&gt;So here is my landmark, my stake in the ground.  Pearl was here.  May this mark out the shape of who she was and leave that impression upon the world for much&amp;nbsp;longer.&lt;/p&gt;
&lt;p&gt;The finality of death resolves so many questions.  I often wished I could improve Pearl&amp;#8217;s tense relationships with Anise and Cheeseball, but now there&amp;#8217;s no problem to solve.  The interactions they had are all the interactions they will ever have.  The tension is gone, now.  The worries about how long Pearl&amp;#8217;s heart will last are gone&amp;nbsp;too.&lt;/p&gt;
&lt;p&gt;The cat dynamic has shifted, again.  Cheeseball and Napoleon have been much more affectionate towards Ash, and Napoleon has suddenly become a lap cat.  I suppose the rest of the cats missed Ash while they were siloed away with Pearl for so long.  Maybe they&amp;#8217;re grieving?  Cats are so open with their emotions, but sometimes they&amp;#8217;re still&amp;nbsp;inscrutable.&lt;/p&gt;
&lt;p&gt;Pearl&amp;#8217;s urn is on the dresser in our bedroom, right next to Twigs.  Hers is bigger than his, somehow.  But that&amp;#8217;s Pearl for you — she always knew how to take up&amp;nbsp;space.&lt;/p&gt;
&lt;p&gt;&amp;#8230;&lt;/p&gt;
&lt;p&gt;No, this is too dire an ending.  Pearl was dealt a bad hand, but she always tried to be nice despite that.  She got to see a lot of places and make a lot of friends, both people and cats and even one dog.  Even when she had complex and skeptical feelings about Anise, she kept trying to be friends with him.  She faltered at times, but she always did her best to uphold her principles of loveliness, strong boundaries, and please give me a&amp;nbsp;treat.&lt;/p&gt;
&lt;p&gt;That&amp;#8217;s a lot for a tiny cat.  I admire her for it, and I will not forget&amp;nbsp;it.&lt;/p&gt;
&lt;p&gt;&lt;div class="prose-full-illustration"&gt;&lt;img src="/media/2022-01-goodbye-pearl/pearl-ash-desk.jpg" alt="Pearl sitting contently next to Ash at their desk"&gt;&lt;/div&gt;&lt;/p&gt;
&lt;p&gt;Thank you for reading about Pearl.  I hope you&amp;#8217;ll remember her too.  We loved her very much, and she put a lot of love back into the world.  If you would like to experience more Pearl, here are some videos of her.  I have some more to sift through, so this list may grow in the coming&amp;nbsp;days.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href="https://www.youtube.com/watch?v=iVBUsFLXknE"&gt;Pearl loves Apollo, our former German&amp;nbsp;Shepherd&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href="https://www.youtube.com/watch?v=j2hj84IkT6A"&gt;Kitten Pearl meows and&amp;nbsp;purrs&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href="https://www.youtube.com/watch?v=m3NhZHr-E0I"&gt;Pearl performs mouse&amp;nbsp;alert&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href="https://www.youtube.com/watch?v=WoQxHUvBQ1o"&gt;Pearl attempts to open the kibble tub with her&amp;nbsp;face&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href="https://www.youtube.com/watch?v=u1DmIYWKSsU"&gt;Pearl asks very clearly for string, without even saying a&amp;nbsp;word&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href="https://www.youtube.com/watch?v=bd4Bvdz-qMc"&gt;Pearl rolls around in a&amp;nbsp;sunbeam&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;And here are some games she has starred in.  Or, rather, her fursona Purrl has starred in&amp;nbsp;them.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href="https://eevee.itch.io/neon-phase"&gt;&lt;span class="caps"&gt;NEON&lt;/span&gt; &lt;span class="caps"&gt;PHASE&lt;/span&gt;&lt;/a&gt; — a shortish platformer with items and puzzles where Purrl is an &lt;span class="caps"&gt;NPC&lt;/span&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href="https://eevee.itch.io/lunar-depot-38"&gt;Lunar Depot 38&lt;/a&gt; — a little platforming shooter where you play as Purrl, with a&amp;nbsp;gun&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href="https://eevee.itch.io/anise-escape-despair"&gt;Star Anise Chronicles: Escape from the Chamber of Despair&lt;/a&gt; — an illustrated text adventure where you must work together with Purrl to&amp;nbsp;escape&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href="https://eevee.itch.io/anise-wheres-twig"&gt;Star Anise Chronicles: Oh No Wheres Twig??&lt;/a&gt; — a short platformer with items and puzzles where Purrl is an &lt;span class="caps"&gt;NPC&lt;/span&gt;, but differently, and made after Twigs&amp;nbsp;died&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href="https://eevee.itch.io/a-very-tiny-purrl-game"&gt;a very tiny purrl game&lt;/a&gt; — a tiny 3D platformer where you play as Purrl and must collect&amp;nbsp;things&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;</content><category term="blog"></category><category term="cats"></category><category term="pearl"></category><category term="personal"></category><category term="eulogy"></category></entry><entry><title>Recommended GZDoom settings</title><link href="https://eev.ee/blog/2021/12/11/recommended-gzdoom-settings/" rel="alternate"></link><published>2021-12-11T18:58:00-08:00</published><updated>2021-12-11T18:58:00-08:00</updated><author><name>Eevee</name></author><id>tag:eev.ee,2021-12-11:/blog/2021/12/11/recommended-gzdoom-settings/</id><summary type="html">&lt;p&gt;&lt;a href="https://zdoom.org/index"&gt;GZDoom&lt;/a&gt; is the fanciest way to play Doom.  Unfortunately, it has also historically been difficult to recommend to newcomers, because its default settings are…  &lt;em&gt;questionable&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;Conspicuously, for over a decade, it defaulted to traditional Doom movement keys (no &lt;span class="caps"&gt;WASD&lt;/span&gt;) and no mouselook.  I am &lt;em&gt;overjoyed&lt;/em&gt; to discover that this is no longer the case, and it plays like a god damn &lt;span class="caps"&gt;FPS&lt;/span&gt; out of the box, but there are still a few twiddles that need twiddling.  Mostly the texture filtering.  Christ, the texture filtering.&lt;/p&gt;
&lt;p&gt;Anyway GZDoom has a lot of options, so here is a handy list of the important ones.  There are fewer than I expected, which is good.&lt;/p&gt;
</summary><content type="html">&lt;p&gt;&lt;a href="https://zdoom.org/index"&gt;GZDoom&lt;/a&gt; is the fanciest way to play Doom.  Unfortunately, it has also historically been difficult to recommend to newcomers, because its default settings are&amp;#8230;  &lt;em&gt;questionable&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;Conspicuously, for over a decade, it defaulted to traditional Doom movement keys (no &lt;span class="caps"&gt;WASD&lt;/span&gt;) and no mouselook.  I am &lt;em&gt;overjoyed&lt;/em&gt; to discover that this is no longer the case, and it plays like a god damn &lt;span class="caps"&gt;FPS&lt;/span&gt; out of the box, but there are still a few twiddles that need twiddling.  Mostly the texture filtering.  Christ, the texture&amp;nbsp;filtering.&lt;/p&gt;
&lt;p&gt;Anyway GZDoom has a lot of options, so here is a handy list of the important ones.  There are fewer than I expected, which is&amp;nbsp;good.&lt;/p&gt;


&lt;hr /&gt;
&lt;p&gt;Note that the routes given to the various settings are for the &lt;em&gt;full&lt;/em&gt; options menu.  Out of the box, GZDoom shows a reduced options menu, &lt;em&gt;because it has a lot of options&lt;/em&gt;.  You can get to the full menu from &lt;code&gt;Full options menu&lt;/code&gt; near the bottom, and from there turn off the simple menu (if you want).  If you get lost, you can also use the option&amp;nbsp;search.&lt;/p&gt;
&lt;p&gt;Also, virtually every setting in GZDoom takes effect &lt;em&gt;instantly&lt;/em&gt;, even while the menu is still visible.  (That&amp;#8217;s why there are no screenshots here!  Just try stuff out yourself.)  It remembers where your cursor was, too, so you can exit the menu to try stuff out, then bring it back up and mash Enter a few times to get back to where you&amp;nbsp;were.&lt;/p&gt;
&lt;h2 id="absolute-necessities"&gt;&lt;a class="toclink" href="#absolute-necessities"&gt;Absolute&amp;nbsp;necessities&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;I do not understand how anyone could argue with&amp;nbsp;these.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Disable texture&amp;nbsp;filtering.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;code&gt;Display options &amp;gt; Texture options &amp;gt; Texture filter mode: None (linear mipmap)&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;By default, GZDoom uses linear upscaling on all sprites and textures, turning them into a blurry mess.  This is objectively ludicrous, since the sprites and textures are &lt;em&gt;pixel art&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;None&lt;/code&gt; restores the crispy aesthetic that God intended — and when I say God, I of course mean John Carmack.  No, wait, maybe I mean Adrian&amp;nbsp;Carmack?&lt;/p&gt;
&lt;p&gt;The &amp;#8220;linear mipmap&amp;#8221; bit means that GZDoom will still use linear &lt;em&gt;downscaling&lt;/em&gt;, so that distant textures still somewhat resemble the actual texture and do not simply collapse into a pixel of arbitrary color.  If you find this objectionable, you may of course simply set it to &lt;code&gt;None&lt;/code&gt;.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Fix the&amp;nbsp;lighting.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;code&gt;Display options &amp;gt; Hardware renderer &amp;gt; Sector light mode: Software&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;GZDoom has half a dozen different lighting models (for&amp;#8230;  some reason), all of which are way off from how Doom actually looked, except for this&amp;nbsp;one.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Fix the partial invisibility&amp;nbsp;effect.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;code&gt;Display options &amp;gt; Hardware renderer &amp;gt; Fuzz style: Software&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;GZDoom defaults to rendering spectres (the harder-to-see variants of the pink demons) with a sort of translucent effect, which is &lt;em&gt;easier&lt;/em&gt; to see, which sort of defeats the purpose of making them harder to&amp;nbsp;see.&lt;/p&gt;
&lt;p&gt;This will emulate the appearance of the original game, scaled up to big chunky pixels.  I actually prefer &lt;code&gt;Smooth fuzz&lt;/code&gt;, which fits better at high resolutions and still looks like a rendering error, but pretty much anything is better than the &lt;code&gt;Shadow&lt;/code&gt; default.&lt;/p&gt;
&lt;p&gt;For testing purposes, it may help to pop open the console with the backtick key (top left) and type &lt;code&gt;summon spectre&lt;/code&gt; to&amp;#8230;  well, summon a&amp;nbsp;spectre.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;And if all you want is something that looks kinda like Doom, you&amp;#8217;re done!  Feel free to stop reading&amp;nbsp;here.&lt;/p&gt;
&lt;p&gt;If you&amp;#8217;re&amp;nbsp;pickier&amp;#8230;&lt;/p&gt;
&lt;h2 id="my-own-preferences"&gt;&lt;a class="toclink" href="#my-own-preferences"&gt;My own&amp;nbsp;preferences&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;These are also all&amp;nbsp;correct.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Always&amp;nbsp;run.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;code&gt;Player setup &amp;gt; Always run: on&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;I don&amp;#8217;t know why you would walk anywhere in Doom.  We&amp;#8217;re in a fucking hurry, man.  There are &lt;em&gt;demons&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;While you&amp;#8217;re here, you may want to set your &lt;code&gt;gender&lt;/code&gt; as appropriate to fix pronouns in obituary messages.  You can also turn autoaim down, or&amp;nbsp;off.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Show a&amp;nbsp;crosshair.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;code&gt;HUD options &amp;gt; Default crosshair: Cross 2&lt;/code&gt;&lt;br /&gt;
&lt;code&gt;HUD options &amp;gt; Scale crosshair: 0.00&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;I just feel better with a little symbol in the middle of the screen.  I&amp;#8217;m holding all my guns at chest height, for some reason, so the sights on those are&amp;nbsp;useless.&lt;/p&gt;
&lt;p&gt;By default the crosshair is humongous, though, hence the&amp;nbsp;scaling.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Speaking of which, &lt;strong&gt;fix the &lt;span class="caps"&gt;HUD&lt;/span&gt;&amp;nbsp;scale.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;code&gt;HUD options &amp;gt; Scaling options &amp;gt; User interface scale: 3&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;The automatic setting is &lt;em&gt;okay&lt;/em&gt; (and better than it used to be), but still leaves some things like pickup messages and the console as microscopic.  I play in a 1080p window on a 1440p monitor, and this seems nice for me.  Adjust as&amp;nbsp;desired.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Use the alternative &lt;span class="caps"&gt;HUD&lt;/span&gt;.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;code&gt;HUD options &amp;gt; Alternative HUD &amp;gt; Enable alternative HUD: On&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;You&amp;#8217;ll need to press &lt;kbd&gt;+&lt;/kbd&gt; until the status bar disappears to actually see&amp;nbsp;it.&lt;/p&gt;
&lt;p&gt;The alternative &lt;span class="caps"&gt;HUD&lt;/span&gt; shows you everything you need to know about the state of the game, while consuming minimal space and still letting you see the weapon sprites in their full glory.  It also shows you a count of kills and secrets, so you have some idea of the progress you&amp;#8217;ve made.  &lt;em&gt;And&lt;/em&gt; it tells you a few things that you had to keep track of yourself in vanilla Doom, like what color of armor you have and whether you have the berserk&amp;nbsp;fist.&lt;/p&gt;
&lt;p&gt;(This replaces a stock fullscreen-with-info &lt;span class="caps"&gt;HUD&lt;/span&gt; that didn&amp;#8217;t exist in vanilla Doom, but which only shows you health, armor, keys, and ammo for your current weapon.  Note that if you play a &lt;span class="caps"&gt;WAD&lt;/span&gt; that heavily alters the game, there&amp;#8217;s a chance it will add custom stuff to the stock &lt;span class="caps"&gt;HUD&lt;/span&gt;, and that stuff &lt;em&gt;will not appear&lt;/em&gt; on the alternative &lt;span class="caps"&gt;HUD&lt;/span&gt;.  It&amp;#8217;s explicitly not&amp;nbsp;moddable.)&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Draw shadows in&amp;nbsp;corners.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;code&gt;Display options &amp;gt; Hardware renderer &amp;gt; Postprocessing &amp;gt; Ambient occlusion quality: Medium&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;Doom has static lighting that affects the walls and floor equally, so the transition from wall to floor/ceiling is pretty flat.  A little &lt;span class="caps"&gt;AO&lt;/span&gt; helps that stand out, even if ambient occlusion is a fake&amp;nbsp;idea.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Fix fake&amp;nbsp;contrast.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;code&gt;Display options &amp;gt; Use fake contrast: Smooth&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;&lt;span class="dquo"&gt;&amp;#8220;&lt;/span&gt;Fake contrast&amp;#8221; refers to a clever trick in the Doom engine wherein horizontal (as seen on the automap) walls draw darker than the room, and vertical walls draw lighter.  In rectangular rooms, this helps avoid the &amp;#8220;flat&amp;#8221; feeling mentioned&amp;nbsp;previously.&lt;/p&gt;
&lt;p&gt;Unfortunately, with complex geometry — as you see frequently in modern maps, but also occasionally in the original ones — this can backfire.  I&amp;#8217;ve been fooled into thinking one particular wall in a curved hallway is a secret, just because it happened to be vertical and appeared lighter than its neighbors.  Meanwhile, rooms at a slant don&amp;#8217;t benefit at&amp;nbsp;all.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;Smooth&lt;/code&gt; preserves the effect, but gradually transitions between the original effect for orthogonal walls and normal lighting for walls at a 45° angle.  (That is, a wall at a 22.5° angle will have half the fake contrast&amp;nbsp;effect.)&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Turn on&amp;nbsp;antialiasing.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;code&gt;Display options &amp;gt; Hardware renderer &amp;gt; Postprocessing &amp;gt; FXAA quality: Low&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;This smooths out lines in the geometry (or straight horizontal lines in textures) when drawn at an angle, without sacrificing those crunchy&amp;nbsp;pixels.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Use&amp;nbsp;particles.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;code&gt;Display options &amp;gt; Hardware renderer &amp;gt; Particle style: Round&lt;/code&gt;&lt;br /&gt;
&lt;code&gt;Display options &amp;gt; Rocket trails: Particles&lt;/code&gt;&lt;br /&gt;
&lt;code&gt;Display options &amp;gt; Blood type: Sprites &amp;amp; particles&lt;/code&gt;&lt;br /&gt;
&lt;code&gt;Display options &amp;gt; Bullet puff type: Sprites &amp;amp; particles&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;The default particles are linear filtered, which looks awful, but I don&amp;#8217;t think anything uses particles by default so you&amp;#8217;d never notice.  You can also set them to &lt;code&gt;Square&lt;/code&gt;, but I think having a single pixel floating in the air looks a bit&amp;nbsp;silly.&lt;/p&gt;
&lt;p&gt;Adding particles to blood splatters and bullet puffs just looks nice.  I replace the rocket trails entirely because the original Doom rocket cloud is just kinda big and clumsy and&amp;nbsp;ugly.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Enable dynamic&amp;nbsp;lighting.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;This is on by default&amp;#8230;  sort of.  GZDoom needs to be able to find the &lt;code&gt;lights.pk3&lt;/code&gt; and &lt;code&gt;brightmaps.pk3&lt;/code&gt; files bundled with it, but if it runs at all, it probably knows where they&amp;nbsp;are.&lt;/p&gt;
&lt;p&gt;So all you have to do is check &lt;code&gt;Load lights&lt;/code&gt; and &lt;code&gt;Load brightmaps&lt;/code&gt; in the little dialog you get when launching the&amp;nbsp;game.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Probably&lt;/em&gt;.  See, for some reason, those checkboxes are only there on Windows — in fact, I didn&amp;#8217;t know they existed at all until two minutes ago.  Even though they set a config setting, they aren&amp;#8217;t accessible via the options menu.  So if that doesn&amp;#8217;t work for you for whatever reason, try popping open the console and&amp;nbsp;doing:&lt;/p&gt;
&lt;p&gt;&lt;code&gt;autoloadlights true
autoloadbrightmaps true&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;Then restart the game.  Glowing objects should now cast (fairly subtle!) light on nearby walls.  You can see this immediately in Doom &lt;span class="caps"&gt;II&lt;/span&gt;&amp;#8217;s first map — there should be a green glow on the floor underneath the armor bonus in the far right corner of the room.  Or for a more dramatic demonstration, &lt;code&gt;IDKFA&lt;/code&gt; and fire a&amp;nbsp;rocket.&lt;/p&gt;
&lt;p&gt;It&amp;#8217;s just a nice touch.  And unlike many attempts to add dynamic lighting to Doom, it&amp;#8217;s not so over-the-top as to be&amp;nbsp;distracting.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="for-the-extremely-ornery"&gt;&lt;a class="toclink" href="#for-the-extremely-ornery"&gt;For the extremely&amp;nbsp;ornery&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;At the other end of the scale, there are those who want an experience as close as possible to vanilla Doom.  Those people might just want to use a port closer to vanilla, like a PRBoom variant or even Chocolate Doom, but GZDoom is willing to do its&amp;nbsp;best:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Quantize light&amp;nbsp;levels.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;code&gt;Display options &amp;gt; Hardware renderer &amp;gt; Banded SW lightmode: On&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;Doom maps support light levels from 0 to 255, but in practice, Doom only understood&amp;#8230;  16, I think?  That&amp;#8217;s because it was a paletted game, and it needed a colormap telling it how to darken each color while still sticking to the palette.  The game only shipped with 15 such mappings, probably because 255 of them would have been ludicrous, and thus there are only 16 light levels in&amp;nbsp;practice.&lt;/p&gt;
&lt;p&gt;GZDoom&amp;#8217;s hardware renderer isn&amp;#8217;t bound by a palette, so it happily supports all 256 light levels.  If you can&amp;#8217;t stand this, well, it can simulate 16 for&amp;nbsp;you.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Disable&amp;nbsp;truecolor.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;code&gt;Display options &amp;gt; Hardware renderer &amp;gt; Postprocessing &amp;gt; Tonemap mode: Palette&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;Doom was a paletted game and only ever displayed the same 256 colors.  You can make the hardware renderer emulate this effect if you really want to.  I don&amp;#8217;t know why you would want&amp;nbsp;to.&lt;/p&gt;
&lt;p&gt;It won&amp;#8217;t be &lt;em&gt;exactly&lt;/em&gt; the same, of course; Doom&amp;#8217;s palette-mapping was handcrafted, whereas the renderer is doing it&amp;nbsp;automatically.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Disable the hardware renderer&amp;nbsp;altogether.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;code&gt;Set video mode &amp;gt; Render mode: True color SW renderer&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;If the very notion of accelerated rendering offends you, the original core of Doom&amp;#8217;s renderer is still in there, just waiting for you.  All you need do is turn it on.  Note that this will severely restrict your ability to mouselook and will draw without vertical perspective, as the Doom renderer was designed around drawing vertical&amp;nbsp;lines.&lt;/p&gt;
&lt;p&gt;What&amp;#8217;s that?  Even true color is too much?  You need the paletted glory that was the best a 386 could do?  Well, &lt;code&gt;Doom software renderer&lt;/code&gt; is also an&amp;nbsp;option.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Disable&amp;nbsp;mouselook.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;code&gt;Mouse options &amp;gt; Always mouselook: Off&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;Doom didn&amp;#8217;t support looking up and down.  Why should&amp;nbsp;you?&lt;/p&gt;
&lt;p&gt;Despite the name, this still allows you to look around &lt;em&gt;horizontally&lt;/em&gt;.  I guess technically that&amp;#8217;s turning, not looking.  Also, moving the mouse up and down will now move you forwards or backwards, just as in vanilla&amp;nbsp;Doom.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Disable &lt;span class="caps"&gt;WASD&lt;/span&gt;.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;code&gt;Customize controls &amp;gt; Preferred keyboard layout: Classic ZDoom&lt;/code&gt;, then &lt;code&gt;Reset to defaults&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;Okay now you have gone too far.  This restores the very keyboard bindings I wanted to rally against — arrow keys to move, turning by default, &lt;kbd&gt;Alt&lt;/kbd&gt; to&amp;nbsp;strafe&amp;#8230;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Disable teleporter&amp;nbsp;zoom.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;code&gt;Display options &amp;gt; Teleporter zoom: Off&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;GZDoom does a brief zoom-in effect on your field of view after (non-silent) teleporting.  Looks sick.  If you hate it, here&amp;#8217;s how to turn it&amp;nbsp;off.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Restore the vanilla lite-amp&amp;nbsp;goggles.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;code&gt;Display options &amp;gt; Hardware renderer &amp;gt; Enhanced night vision mode: Off&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;In vanilla Doom, the lite-amp goggles simply make the entire world render as fullbright, which looks fucking terrible.  GZDoom defaults to a &amp;#8220;night vision goggles&amp;#8221; sort of effect that also highlights objects, but if you really can&amp;#8217;t stand that, this twiddle is here for&amp;nbsp;you.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Enable randomized pitch on sound&amp;nbsp;effects.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;code&gt;Sound options &amp;gt; Randomize pitches: On&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;For the &lt;em&gt;very&lt;/em&gt; ornery, I believe this behavior was in the original release of Doom but (accidentally?) broken in Doom 1.2 and all later versions.  It&amp;#8217;s really weird, but it&amp;#8217;s the intended behavior, I&amp;nbsp;guess!&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Restore Doom&amp;#8217;s automap&amp;nbsp;colors.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;code&gt;Automap options &amp;gt; Map color set: Traditional Doom&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;This will change the automap back to its red-and-yellow-on-black&amp;nbsp;glory.&lt;/p&gt;
&lt;p&gt;It will also remove the colors that tell you where locked doors and the exit are.  You might argue that those are cheating.  I argue that they are the entire point of a&amp;nbsp;map.&lt;/p&gt;
&lt;p&gt;You can also turn off the automap&amp;#8217;s monster and secret counts here if you truly wish to be as lost as&amp;nbsp;possible.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Twiddle with compatibility&amp;nbsp;settings.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;code&gt;Compatibility options &amp;gt; Compatibility mode: ?&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;You might want &lt;code&gt;Doom (strict)&lt;/code&gt; for the closest vanilla experience that GZDoom can provide.  &lt;em&gt;Might&lt;/em&gt;.  The most notable effects&amp;nbsp;are:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Monsters will wake up when seeing a player with a blur sphere.  By default, they usually won&amp;#8217;t, a behavior inherited from&amp;nbsp;Hexen.&lt;/li&gt;
&lt;li&gt;Arch-viles can resurrect crushed corpses as &amp;#8220;ghosts&amp;#8221; that cannot be shot, only harmed by splash damage from&amp;nbsp;rockets.&lt;/li&gt;
&lt;li&gt;Pain elementals will be unable to spawn new lost souls if there are at least 21 already present in the&amp;nbsp;level.&lt;/li&gt;
&lt;li&gt;Monsters can&amp;#8217;t be knocked off of high&amp;nbsp;ledges.&lt;/li&gt;
&lt;li&gt;You will be unable to crowdsurf, meaning you will be blocked both by imps at the foot of a cliff below you, and by cacodemons flying above&amp;nbsp;you.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;You can also toggle these on or off individually at your&amp;nbsp;leisure.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;</content><category term="blog"></category><category term="tech"></category><category term="doom"></category></entry><entry><title>Gamedev from scratch 1: Scaffolding</title><link href="https://eev.ee/blog/2021/01/26/gamedev-from-scratch-1-scaffolding/" rel="alternate"></link><published>2021-01-26T18:27:00-08:00</published><updated>2021-01-26T18:27:00-08:00</updated><author><name>Eevee</name></author><id>tag:eev.ee,2021-01-26:/blog/2021/01/26/gamedev-from-scratch-1-scaffolding/</id><summary type="html">&lt;p&gt;Welcome to part 1 of this narrative series about writing a complete video game from scratch, using the &lt;span class="caps"&gt;PICO&lt;/span&gt;-8.  This is actually the second part, because in this house (unlike Lua) we index from 0, so if you’re new here you may want to consult the introductory stuff and table of contents in &lt;a href="https://eev.ee/blog/2020/11/30/gamedev-from-scratch-0-groundwork/"&gt;part zero&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;If you’ve been following along, welcome back, and let’s dive right in!&lt;/p&gt;
</summary><content type="html">&lt;p&gt;Welcome to part 1 of this narrative series about writing a complete video game from scratch, using the &lt;span class="caps"&gt;PICO&lt;/span&gt;-8.  This is actually the second part, because in this house (unlike Lua) we index from 0, so if you&amp;#8217;re new here you may want to consult the introductory stuff and table of contents in &lt;a href="https://eev.ee/blog/2020/11/30/gamedev-from-scratch-0-groundwork/"&gt;part zero&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;If you&amp;#8217;ve been following along, welcome back, and let&amp;#8217;s dive right&amp;nbsp;in!&lt;/p&gt;


&lt;p&gt;&lt;strong&gt;&lt;a href="https://eev.ee/blog/2020/11/30/gamedev-from-scratch-0-groundwork/"&gt;← Part 0:&amp;nbsp;Groundwork&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;
&lt;h2 id="recap-and-short-term-plans"&gt;&lt;a class="toclink" href="#recap-and-short-term-plans"&gt;Recap and short-term&amp;nbsp;plans&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;So far, I have&amp;#8230;  this.  Which is something, and certainly much more than nothing, but all told not a&amp;nbsp;lot.&lt;/p&gt;
&lt;div class="prose-full-illustration"&gt;
&lt;img src="https://eev.ee/media/gamedev-from-scratch/v1-anise-moving3.gif" alt="Star Anise walking around the screen and turning to face the way he's moving"&gt;
&lt;/div&gt;

&lt;p&gt;Most conspicuously, this is going to be a &lt;em&gt;platformer&lt;/em&gt;, so I need gravity.  The problem with gravity is that it means things are always moving downwards, and if there&amp;#8217;s nothing to stop them, they will continue off indefinitely into the&amp;nbsp;void.&lt;/p&gt;
&lt;p&gt;What I am trying to say here is that I feel the looming spectre of collision detection hanging over me.  I&amp;#8217;m going to need it, and I&amp;#8217;m going to need it &lt;em&gt;real soon&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;And, hey, that sucks.  Collision detection is a real big pain in the ass to write, so needing it this early is a hell of a big spike in the learning curve.  Luckily for you, someone else has already written it:&amp;nbsp;me!&lt;/p&gt;
&lt;p&gt;Before I can get to that, though, I need to add some structure to the code I have so far.  Everything I&amp;#8217;ve written is designed to work for Star Anise &lt;em&gt;and only&lt;/em&gt; Star Anise.  That&amp;#8217;s perfectly fine when he&amp;#8217;s the only thing in the game, but I don&amp;#8217;t expect he&amp;#8217;ll stay alone for long!  Collision detection in particular is a pretty major component of a platformer, so I definitely want to be able to reuse it for other things in the game.  Also, collision detection is a big fucking hairy mess, so I definitely want to be able to shove it in a corner somewhere I&amp;#8217;ll never have to look at it&amp;nbsp;again.&lt;/p&gt;
&lt;p&gt;A good start would be to build towards having a corner to shove it&amp;nbsp;into.&lt;/p&gt;
&lt;h2 id="adding-some-structure"&gt;&lt;a class="toclink" href="#adding-some-structure"&gt;Adding some&amp;nbsp;structure&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;As of where I left off last time, my special &lt;code&gt;_update()&lt;/code&gt; and &lt;code&gt;_draw()&lt;/code&gt; functions are mostly full of code for updating and drawing Star Anise.  That doesn&amp;#8217;t really sit right with me; as the main entry points, they should be about updating and drawing &lt;em&gt;the game itself&lt;/em&gt;.  Star Anise is &lt;em&gt;part of&lt;/em&gt; the game, but he isn&amp;#8217;t the whole game.  All that code that&amp;#8217;s specific to him should be put off in a little box somewhere.  Cats love to be in little boxes, you&amp;nbsp;see.&lt;/p&gt;
&lt;p&gt;This raises the question of how I want to structure this project in general.  And, I note: structuring a software project is &lt;em&gt;hard&lt;/em&gt;, and you only really get a good sense of how to do it from experience.  I&amp;#8217;m still not sure &lt;em&gt;I&lt;/em&gt; have a good sense of how to do it.  Hell, I&amp;#8217;m not convinced &lt;em&gt;anyone&lt;/em&gt; has a good sense of how to do&amp;nbsp;it.&lt;/p&gt;
&lt;p&gt;Thankfully, this is a game, so it&amp;#8217;s pretty obvious how to break it into pieces.  (The tradeoff is that everything in a game ends up entangled with everything else no matter how you structure it, alas.)  Star Anise is a separate &lt;em&gt;thing&lt;/em&gt; in the game, so he might as well be a separate &lt;em&gt;thing&lt;/em&gt; in the code.  Later on I&amp;#8217;ll need some more abstract structuring, but as an extremely rough guideline: if I can give it a name, it&amp;#8217;s a good candidate to be made into a &lt;em&gt;thing&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;But what, exactly, is a &lt;em&gt;thing&lt;/em&gt; in code?  Most commonly (but not always), a &lt;em&gt;thing&lt;/em&gt; is implemented with what&amp;#8217;s called an &lt;em&gt;object&lt;/em&gt; — a little bundle of data (what it &lt;em&gt;is&lt;/em&gt;) with code (what it can &lt;em&gt;do&lt;/em&gt;).  I already have both of these parts for Star Anise: he has data like his position and which way he&amp;#8217;s facing, and he has code for doing things like updating or drawing himself.  A great first step would be to extract that stuff into an object, after which some other structure might reveal&amp;nbsp;itself.&lt;/p&gt;
&lt;p&gt;I do need to do one thing before I can turn get to that, though.  You see, Lua is one of the few languages in common use today that doesn&amp;#8217;t &lt;em&gt;quite&lt;/em&gt; have built-in support for objects.  Instead, it has all the building blocks you need to craft your own system for making objects.  On the one hand, the way it does that is very slick and clever.  On the other hand, it means you can&amp;#8217;t write much Lua without cobbling together some arcane nonsense first, and also no one&amp;#8217;s code quite works the same&amp;nbsp;way.&lt;/p&gt;
&lt;p&gt;Which brings me to the following magnificent&amp;nbsp;monstrosity:&lt;/p&gt;
&lt;table class="highlighttable"&gt;&lt;tr&gt;&lt;td class="linenos"&gt;&lt;div class="linenodiv"&gt;&lt;pre&gt;&lt;span class="normal"&gt; 1&lt;/span&gt;
&lt;span class="normal"&gt; 2&lt;/span&gt;
&lt;span class="normal"&gt; 3&lt;/span&gt;
&lt;span class="normal"&gt; 4&lt;/span&gt;
&lt;span class="normal"&gt; 5&lt;/span&gt;
&lt;span class="normal"&gt; 6&lt;/span&gt;
&lt;span class="normal"&gt; 7&lt;/span&gt;
&lt;span class="normal"&gt; 8&lt;/span&gt;
&lt;span class="normal"&gt; 9&lt;/span&gt;
&lt;span class="normal"&gt;10&lt;/span&gt;
&lt;span class="normal"&gt;11&lt;/span&gt;
&lt;span class="normal"&gt;12&lt;/span&gt;
&lt;span class="normal"&gt;13&lt;/span&gt;
&lt;span class="normal"&gt;14&lt;/span&gt;
&lt;span class="normal"&gt;15&lt;/span&gt;
&lt;span class="normal"&gt;16&lt;/span&gt;
&lt;span class="normal"&gt;17&lt;/span&gt;
&lt;span class="normal"&gt;18&lt;/span&gt;
&lt;span class="normal"&gt;19&lt;/span&gt;
&lt;span class="normal"&gt;20&lt;/span&gt;
&lt;span class="normal"&gt;21&lt;/span&gt;
&lt;span class="normal"&gt;22&lt;/span&gt;
&lt;span class="normal"&gt;23&lt;/span&gt;
&lt;span class="normal"&gt;24&lt;/span&gt;
&lt;span class="normal"&gt;25&lt;/span&gt;
&lt;span class="normal"&gt;26&lt;/span&gt;
&lt;span class="normal"&gt;27&lt;/span&gt;
&lt;span class="normal"&gt;28&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/td&gt;&lt;td class="code"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="kr"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;nop&lt;/span&gt;&lt;span class="p"&gt;(...)&lt;/span&gt; &lt;span class="kr"&gt;return&lt;/span&gt; &lt;span class="p"&gt;...&lt;/span&gt; &lt;span class="kr"&gt;end&lt;/span&gt;

&lt;span class="c1"&gt;--------------------------------&lt;/span&gt;
&lt;span class="c1"&gt;-- simple object type&lt;/span&gt;
&lt;span class="kd"&gt;local&lt;/span&gt; &lt;span class="n"&gt;obj&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;init&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;nop&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="n"&gt;obj&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;__index&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;obj&lt;/span&gt;

&lt;span class="kr"&gt;function&lt;/span&gt; &lt;span class="nc"&gt;obj&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="nf"&gt;__call&lt;/span&gt;&lt;span class="p"&gt;(...)&lt;/span&gt;
    &lt;span class="kd"&gt;local&lt;/span&gt; &lt;span class="n"&gt;o&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;setmetatable&lt;/span&gt;&lt;span class="p"&gt;({},&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="kr"&gt;return&lt;/span&gt; &lt;span class="n"&gt;o&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;o&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="n"&gt;init&lt;/span&gt;&lt;span class="p"&gt;(...)&lt;/span&gt;
&lt;span class="kr"&gt;end&lt;/span&gt;

&lt;span class="c1"&gt;-- subclassing&lt;/span&gt;
&lt;span class="kr"&gt;function&lt;/span&gt; &lt;span class="nc"&gt;obj&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="nf"&gt;extend&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;proto&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;proto&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;proto&lt;/span&gt; &lt;span class="ow"&gt;or&lt;/span&gt; &lt;span class="p"&gt;{}&lt;/span&gt;

    &lt;span class="c1"&gt;-- copy meta values, since lua doesn&amp;#39;t walk the prototype chain to find them&lt;/span&gt;
    &lt;span class="kr"&gt;for&lt;/span&gt; &lt;span class="n"&gt;k&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;v&lt;/span&gt; &lt;span class="kr"&gt;in&lt;/span&gt; &lt;span class="nb"&gt;pairs&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="kr"&gt;do&lt;/span&gt;
        &lt;span class="kr"&gt;if&lt;/span&gt; &lt;span class="n"&gt;sub&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;k&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;__&amp;quot;&lt;/span&gt; &lt;span class="kr"&gt;then&lt;/span&gt;
            &lt;span class="n"&gt;proto&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;k&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;v&lt;/span&gt;
        &lt;span class="kr"&gt;end&lt;/span&gt;
    &lt;span class="kr"&gt;end&lt;/span&gt;

    &lt;span class="n"&gt;proto&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;__index&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;proto&lt;/span&gt;
    &lt;span class="n"&gt;proto&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;__super&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;

    &lt;span class="kr"&gt;return&lt;/span&gt; &lt;span class="nb"&gt;setmetatable&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;proto&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="kr"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;p&gt;How does this work?  What does this mean?  What &lt;em&gt;is&lt;/em&gt; a prototype chain, anyway?  Dearest reader: it extremely does not matter.  No one cares.  I would have to stare at this for ten minutes to even begin to explain it.  Every line is oozing with subtlety.  To be honest, even though I describe this series as &amp;#8220;from scratch&amp;#8221;, this is one of the very few things that I copy/pasted wholesale from an earlier game.  I know this does the bare minimum I need and I absolutely do not want to waste time reinventing it incorrectly.  To drive that point home: I wrote &lt;em&gt;collision detection&lt;/em&gt; from scratch, but I &lt;em&gt;copy/pasted this&lt;/em&gt;.  (But if you really want to know, I&amp;#8217;ll explain it in an&amp;nbsp;appendix.)&lt;/p&gt;
&lt;p&gt;Feel free to copy/paste mine, if you like.  You can also find a number of tiny Lua object systems floating around online, but with tokens at a premium, I wanted something &lt;em&gt;microscopic&lt;/em&gt;.  This basically does constructors, inheritance, and nothing&amp;nbsp;else.&lt;/p&gt;
&lt;p&gt;(Oh, I don&amp;#8217;t think I mentioned, but the &lt;code&gt;--&lt;/code&gt; prefix indicates a Lua &lt;em&gt;comment&lt;/em&gt;.  Comments are ignored by the computer and tend to contain notes that are helpful for humans to follow.  They don&amp;#8217;t count against the &lt;span class="caps"&gt;PICO&lt;/span&gt;-8 token limit, but they &lt;em&gt;do&lt;/em&gt; count against the total size limit,&amp;nbsp;alas.)&lt;/p&gt;
&lt;p&gt;The upshot is that I can now write stuff like&amp;nbsp;this:&lt;/p&gt;
&lt;table class="highlighttable"&gt;&lt;tr&gt;&lt;td class="linenos"&gt;&lt;div class="linenodiv"&gt;&lt;pre&gt;&lt;span class="normal"&gt; 1&lt;/span&gt;
&lt;span class="normal"&gt; 2&lt;/span&gt;
&lt;span class="normal"&gt; 3&lt;/span&gt;
&lt;span class="normal"&gt; 4&lt;/span&gt;
&lt;span class="normal"&gt; 5&lt;/span&gt;
&lt;span class="normal"&gt; 6&lt;/span&gt;
&lt;span class="normal"&gt; 7&lt;/span&gt;
&lt;span class="normal"&gt; 8&lt;/span&gt;
&lt;span class="normal"&gt; 9&lt;/span&gt;
&lt;span class="normal"&gt;10&lt;/span&gt;
&lt;span class="normal"&gt;11&lt;/span&gt;
&lt;span class="normal"&gt;12&lt;/span&gt;
&lt;span class="normal"&gt;13&lt;/span&gt;
&lt;span class="normal"&gt;14&lt;/span&gt;
&lt;span class="normal"&gt;15&lt;/span&gt;
&lt;span class="normal"&gt;16&lt;/span&gt;
&lt;span class="normal"&gt;17&lt;/span&gt;
&lt;span class="normal"&gt;18&lt;/span&gt;
&lt;span class="normal"&gt;19&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/td&gt;&lt;td class="code"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="kd"&gt;local&lt;/span&gt; &lt;span class="n"&gt;vec&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;obj&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="n"&gt;extend&lt;/span&gt;&lt;span class="p"&gt;{}&lt;/span&gt;

&lt;span class="kr"&gt;function&lt;/span&gt; &lt;span class="nc"&gt;vec&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="nf"&gt;init&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;y&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;x&lt;/span&gt; &lt;span class="ow"&gt;or&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;
    &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;y&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;y&lt;/span&gt; &lt;span class="ow"&gt;or&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;
&lt;span class="kr"&gt;end&lt;/span&gt;

&lt;span class="kr"&gt;function&lt;/span&gt; &lt;span class="nc"&gt;vec&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="nf"&gt;__add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="kr"&gt;return&lt;/span&gt; &lt;span class="n"&gt;vec&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;y&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;y&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="kr"&gt;end&lt;/span&gt;

&lt;span class="kr"&gt;function&lt;/span&gt; &lt;span class="nc"&gt;vec&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="nf"&gt;__sub&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="kr"&gt;return&lt;/span&gt; &lt;span class="n"&gt;vec&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;y&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;y&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="kr"&gt;end&lt;/span&gt;

&lt;span class="kr"&gt;function&lt;/span&gt; &lt;span class="nc"&gt;vec&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="nf"&gt;iadd&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt;
    &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;y&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;y&lt;/span&gt;
&lt;span class="kr"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;p&gt;This creates a&amp;#8230;  well, terminology is tricky, but I&amp;#8217;ll call it a &lt;em&gt;type&lt;/em&gt; while doing air-quotes and glancing behind me to see if any Haskell programmers are listening.  (It&amp;#8217;s not much like the notion of a type in many other languages, but it&amp;#8217;s the closest I&amp;#8217;m going to get.)  Now I can combine an x- and y-coordinate together as a single object, a single &lt;em&gt;thing&lt;/em&gt;, without having to juggle them separately.  I&amp;#8217;m calling that kind of thing a &lt;code&gt;vec&lt;/code&gt;, short for &lt;em&gt;vector&lt;/em&gt;, the name mathematicians give to a set of coordinates.  (More or less.  That&amp;#8217;s not quite right, but don&amp;#8217;t worry about it&amp;nbsp;yet.)&lt;/p&gt;
&lt;aside class="aside--computers-are-bad"&gt;
&lt;p&gt;"Vector" is also the name C++ and Rust programmers give to a &lt;em&gt;resizeable&lt;/em&gt; list of things.  I told you, we are awful at&amp;nbsp;naming.&lt;/p&gt;
&lt;/aside&gt;
&lt;aside class="aside--look-out"&gt;
&lt;p&gt;Usually, I'd want to use so-called CamelCase names for types (so &lt;code&gt;Vec&lt;/code&gt; or &lt;code&gt;Vector&lt;/code&gt; or &lt;code&gt;Vector2&lt;/code&gt;) and lowercase names for values (so &lt;code&gt;vec&lt;/code&gt; would be &lt;em&gt;a particular vector&lt;/em&gt;).  Unfortunately, the PICO-8 editor doesn't do capital letters — or maybe only does capital letters — so I just have to be careful.  The total amount of code I can write is pretty limited, so I won't have too many types anyway, and hopefully I can remember which names are one of the handful of types I've&amp;nbsp;defined.&lt;/p&gt;
&lt;/aside&gt;
&lt;p&gt;After the above incantation, I can create &lt;em&gt;a&lt;/em&gt; &lt;code&gt;vec&lt;/code&gt; by calling it like a function.  Note that the arguments ultimately arrive in &lt;code&gt;vec:init&lt;/code&gt;, loosely called a &lt;em&gt;constructor&lt;/em&gt;, which stores them in &lt;code&gt;self.x&lt;/code&gt; and &lt;code&gt;self.y&lt;/code&gt; — where &lt;code&gt;self&lt;/code&gt; is the &lt;code&gt;vec&lt;/code&gt; being&amp;nbsp;created.&lt;/p&gt;
&lt;table class="highlighttable"&gt;&lt;tr&gt;&lt;td class="linenos"&gt;&lt;div class="linenodiv"&gt;&lt;pre&gt;&lt;span class="normal"&gt;1&lt;/span&gt;
&lt;span class="normal"&gt;2&lt;/span&gt;
&lt;span class="normal"&gt;3&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/td&gt;&lt;td class="code"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="c1"&gt;-- this is example code, not part of the game&lt;/span&gt;
&lt;span class="kd"&gt;local&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;vec&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;x = &amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot; y = &amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;y&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;  &lt;span class="c1"&gt;-- x = 1 y = 2&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;p&gt;That &lt;code&gt;iadd&lt;/code&gt; thing is a &lt;em&gt;method&lt;/em&gt;, a special function that I can call &lt;em&gt;on&lt;/em&gt; a &lt;code&gt;vec&lt;/code&gt;.  It&amp;#8217;s like every &lt;code&gt;vec&lt;/code&gt; carries around its own little bag of functions anywhere it appears — and since they&amp;#8217;re specific to &lt;code&gt;vec&lt;/code&gt;, I don&amp;#8217;t have to worry about reusing names.  (In fact, reusing names can be very helpful, as we&amp;#8217;ll see&amp;nbsp;later!)&lt;/p&gt;
&lt;p&gt;The name &lt;code&gt;iadd&lt;/code&gt; is (very!) short for &amp;#8220;in-place add&amp;#8221;, suggesting that the first vector adds the second vector &lt;em&gt;to itself&lt;/em&gt; rather than creating a new third vector.  That&amp;#8217;s something I expect to be doing a lot, and making a method for it saves me some precious&amp;nbsp;tokens.&lt;/p&gt;
&lt;table class="highlighttable"&gt;&lt;tr&gt;&lt;td class="linenos"&gt;&lt;div class="linenodiv"&gt;&lt;pre&gt;&lt;span class="normal"&gt;1&lt;/span&gt;
&lt;span class="normal"&gt;2&lt;/span&gt;
&lt;span class="normal"&gt;3&lt;/span&gt;
&lt;span class="normal"&gt;4&lt;/span&gt;
&lt;span class="normal"&gt;5&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/td&gt;&lt;td class="code"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="c1"&gt;-- example code&lt;/span&gt;
&lt;span class="kd"&gt;local&lt;/span&gt; &lt;span class="n"&gt;v&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;vec&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="kd"&gt;local&lt;/span&gt; &lt;span class="n"&gt;w&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;vec&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="n"&gt;iadd&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;w&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;x = &amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot; y = &amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;y&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;  &lt;span class="c1"&gt;-- x = 4 y = 6&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;aside class="aside--look-out"&gt;
&lt;p&gt;Methods in Lua are called with a &lt;strong&gt;colon&lt;/strong&gt;!  If you write &lt;code&gt;v.iadd(w)&lt;/code&gt; instead, either you'll get an extremely cryptic error or something very wrong will happen.  Sorry; this is one of Lua's subtle pitfalls and there's not really any good way to prevent&amp;nbsp;it.&lt;/p&gt;
&lt;/aside&gt;
&lt;p&gt;Finally, those funny &lt;code&gt;__add&lt;/code&gt; and &lt;code&gt;__sub&lt;/code&gt; methods are special to Lua (if enchanted correctly, which is part of what the &lt;code&gt;obj&lt;/code&gt; gobbledygook does) — they let me use &lt;code&gt;+&lt;/code&gt; and &lt;code&gt;-&lt;/code&gt; on my &lt;code&gt;vec&lt;/code&gt;s just like they were&amp;nbsp;numbers.&lt;/p&gt;
&lt;table class="highlighttable"&gt;&lt;tr&gt;&lt;td class="linenos"&gt;&lt;div class="linenodiv"&gt;&lt;pre&gt;&lt;span class="normal"&gt;1&lt;/span&gt;
&lt;span class="normal"&gt;2&lt;/span&gt;
&lt;span class="normal"&gt;3&lt;/span&gt;
&lt;span class="normal"&gt;4&lt;/span&gt;
&lt;span class="normal"&gt;5&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/td&gt;&lt;td class="code"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="c1"&gt;-- example code&lt;/span&gt;
&lt;span class="kd"&gt;local&lt;/span&gt; &lt;span class="n"&gt;q&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;vec&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="kd"&gt;local&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;vec&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="kd"&gt;local&lt;/span&gt; &lt;span class="n"&gt;s&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;q&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt;
&lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;x = &amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot; y = &amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;y&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;  &lt;span class="c1"&gt;-- x = 4 y = 6&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;aside class="aside--note-from-future"&gt;
&lt;p&gt;I find myself wondering why I made an &lt;code&gt;iadd&lt;/code&gt; method when I could have just used &lt;code&gt;+=&lt;/code&gt;.  The final game only even uses &lt;code&gt;iadd&lt;/code&gt; four times, and it's more tokens than &lt;code&gt;+=&lt;/code&gt;!&lt;/p&gt;
&lt;/aside&gt;
&lt;p&gt;This is the core idea of objects.  A &lt;code&gt;vec&lt;/code&gt; has some data — &lt;code&gt;x&lt;/code&gt; and &lt;code&gt;y&lt;/code&gt; — and some code — for adding another &lt;code&gt;vec&lt;/code&gt; to itself.  If I later discover some new thing I want a &lt;code&gt;vec&lt;/code&gt; to be able to do, I can add another method here, and it&amp;#8217;ll be available on every &lt;code&gt;vec&lt;/code&gt; throughout my game.  I can repeat myself a little bit less, &lt;em&gt;and&lt;/em&gt; I can keep these related ideas together, separate from everything&amp;nbsp;else.&lt;/p&gt;
&lt;p&gt;Get the basic jist?  I hope so, because I&amp;#8217;ve really gotta get a move on&amp;nbsp;here.&lt;/p&gt;
&lt;h2 id="objectifying-star-anise"&gt;&lt;a class="toclink" href="#objectifying-star-anise"&gt;Objectifying Star&amp;nbsp;Anise&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Now that I have a way to define objects, I can turn Star Anise into&amp;nbsp;one.&lt;/p&gt;
&lt;table class="highlighttable"&gt;&lt;tr&gt;&lt;td class="linenos"&gt;&lt;div class="linenodiv"&gt;&lt;pre&gt;&lt;span class="normal"&gt; 1&lt;/span&gt;
&lt;span class="normal"&gt; 2&lt;/span&gt;
&lt;span class="normal"&gt; 3&lt;/span&gt;
&lt;span class="normal"&gt; 4&lt;/span&gt;
&lt;span class="normal"&gt; 5&lt;/span&gt;
&lt;span class="normal"&gt; 6&lt;/span&gt;
&lt;span class="normal"&gt; 7&lt;/span&gt;
&lt;span class="normal"&gt; 8&lt;/span&gt;
&lt;span class="normal"&gt; 9&lt;/span&gt;
&lt;span class="normal"&gt;10&lt;/span&gt;
&lt;span class="normal"&gt;11&lt;/span&gt;
&lt;span class="normal"&gt;12&lt;/span&gt;
&lt;span class="normal"&gt;13&lt;/span&gt;
&lt;span class="normal"&gt;14&lt;/span&gt;
&lt;span class="normal"&gt;15&lt;/span&gt;
&lt;span class="normal"&gt;16&lt;/span&gt;
&lt;span class="normal"&gt;17&lt;/span&gt;
&lt;span class="normal"&gt;18&lt;/span&gt;
&lt;span class="normal"&gt;19&lt;/span&gt;
&lt;span class="normal"&gt;20&lt;/span&gt;
&lt;span class="normal"&gt;21&lt;/span&gt;
&lt;span class="normal"&gt;22&lt;/span&gt;
&lt;span class="normal"&gt;23&lt;/span&gt;
&lt;span class="normal"&gt;24&lt;/span&gt;
&lt;span class="normal"&gt;25&lt;/span&gt;
&lt;span class="normal"&gt;26&lt;/span&gt;
&lt;span class="normal"&gt;27&lt;/span&gt;
&lt;span class="normal"&gt;28&lt;/span&gt;
&lt;span class="normal"&gt;29&lt;/span&gt;
&lt;span class="normal"&gt;30&lt;/span&gt;
&lt;span class="normal"&gt;31&lt;/span&gt;
&lt;span class="normal"&gt;32&lt;/span&gt;
&lt;span class="normal"&gt;33&lt;/span&gt;
&lt;span class="normal"&gt;34&lt;/span&gt;
&lt;span class="normal"&gt;35&lt;/span&gt;
&lt;span class="normal"&gt;36&lt;/span&gt;
&lt;span class="normal"&gt;37&lt;/span&gt;
&lt;span class="normal"&gt;38&lt;/span&gt;
&lt;span class="normal"&gt;39&lt;/span&gt;
&lt;span class="normal"&gt;40&lt;/span&gt;
&lt;span class="normal"&gt;41&lt;/span&gt;
&lt;span class="normal"&gt;42&lt;/span&gt;
&lt;span class="normal"&gt;43&lt;/span&gt;
&lt;span class="normal"&gt;44&lt;/span&gt;
&lt;span class="normal"&gt;45&lt;/span&gt;
&lt;span class="normal"&gt;46&lt;/span&gt;
&lt;span class="normal"&gt;47&lt;/span&gt;
&lt;span class="normal"&gt;48&lt;/span&gt;
&lt;span class="normal"&gt;49&lt;/span&gt;
&lt;span class="normal"&gt;50&lt;/span&gt;
&lt;span class="normal"&gt;51&lt;/span&gt;
&lt;span class="normal"&gt;52&lt;/span&gt;
&lt;span class="normal"&gt;53&lt;/span&gt;
&lt;span class="normal"&gt;54&lt;/span&gt;
&lt;span class="normal"&gt;55&lt;/span&gt;
&lt;span class="normal"&gt;56&lt;/span&gt;
&lt;span class="normal"&gt;57&lt;/span&gt;
&lt;span class="normal"&gt;58&lt;/span&gt;
&lt;span class="normal"&gt;59&lt;/span&gt;
&lt;span class="normal"&gt;60&lt;/span&gt;
&lt;span class="normal"&gt;61&lt;/span&gt;
&lt;span class="normal"&gt;62&lt;/span&gt;
&lt;span class="normal"&gt;63&lt;/span&gt;
&lt;span class="normal"&gt;64&lt;/span&gt;
&lt;span class="normal"&gt;65&lt;/span&gt;
&lt;span class="normal"&gt;66&lt;/span&gt;
&lt;span class="normal"&gt;67&lt;/span&gt;
&lt;span class="normal"&gt;68&lt;/span&gt;
&lt;span class="normal"&gt;69&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/td&gt;&lt;td class="code"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="kr"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;b2n&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="kr"&gt;return&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt; &lt;span class="ow"&gt;and&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="ow"&gt;or&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;
&lt;span class="kr"&gt;end&lt;/span&gt;

&lt;span class="kd"&gt;local&lt;/span&gt; &lt;span class="n"&gt;t&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;
&lt;span class="kd"&gt;local&lt;/span&gt; &lt;span class="n"&gt;player&lt;/span&gt;

&lt;span class="kd"&gt;local&lt;/span&gt; &lt;span class="n"&gt;anise_stand&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;17&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;18&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;33&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;34&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="kd"&gt;local&lt;/span&gt; &lt;span class="n"&gt;anise_jump&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;17&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;18&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;19&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;35&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="kd"&gt;local&lt;/span&gt; &lt;span class="n"&gt;anise&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;obj&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="n"&gt;extend&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;move&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;vec&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
    &lt;span class="n"&gt;left&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kr"&gt;function&lt;/span&gt; &lt;span class="nc"&gt;anise&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="nf"&gt;init&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;pos&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;pos&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;pos&lt;/span&gt;
&lt;span class="kr"&gt;end&lt;/span&gt;

&lt;span class="kr"&gt;function&lt;/span&gt; &lt;span class="nc"&gt;anise&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="nf"&gt;update&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="kr"&gt;if&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;move&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt; &lt;span class="kr"&gt;then&lt;/span&gt;
        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;left&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;
    &lt;span class="kr"&gt;elseif&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;move&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt; &lt;span class="kr"&gt;then&lt;/span&gt;
        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;left&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
    &lt;span class="kr"&gt;end&lt;/span&gt;

    &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;pos&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="n"&gt;iadd&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;move&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="kr"&gt;end&lt;/span&gt;

&lt;span class="kr"&gt;function&lt;/span&gt; &lt;span class="nc"&gt;anise&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="nf"&gt;draw&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="kd"&gt;local&lt;/span&gt; &lt;span class="n"&gt;pose&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;anise_stand&lt;/span&gt;
    &lt;span class="kr"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;move&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt; &lt;span class="o"&gt;~=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt; &lt;span class="ow"&gt;or&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;move&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;y&lt;/span&gt; &lt;span class="o"&gt;~=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="ow"&gt;and&lt;/span&gt; &lt;span class="n"&gt;t&lt;/span&gt; &lt;span class="o"&gt;%&lt;/span&gt; &lt;span class="mi"&gt;8&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="mi"&gt;4&lt;/span&gt; &lt;span class="kr"&gt;then&lt;/span&gt;
        &lt;span class="n"&gt;pose&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;anise_jump&lt;/span&gt;
    &lt;span class="kr"&gt;end&lt;/span&gt;
    &lt;span class="kd"&gt;local&lt;/span&gt; &lt;span class="n"&gt;y&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;pos&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;y&lt;/span&gt;
    &lt;span class="kd"&gt;local&lt;/span&gt; &lt;span class="n"&gt;x0&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;pos&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt;
    &lt;span class="kd"&gt;local&lt;/span&gt; &lt;span class="n"&gt;dx&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;8&lt;/span&gt;
    &lt;span class="kr"&gt;if&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;left&lt;/span&gt; &lt;span class="kr"&gt;then&lt;/span&gt;
        &lt;span class="n"&gt;dx&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;8&lt;/span&gt;
        &lt;span class="n"&gt;x0&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="mi"&gt;8&lt;/span&gt;
    &lt;span class="kr"&gt;end&lt;/span&gt;
    &lt;span class="kd"&gt;local&lt;/span&gt; &lt;span class="n"&gt;x&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;x0&lt;/span&gt;
    &lt;span class="kr"&gt;for&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;#&lt;/span&gt;&lt;span class="n"&gt;pose&lt;/span&gt; &lt;span class="kr"&gt;do&lt;/span&gt;
        &lt;span class="n"&gt;spr&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;pose&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;y&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;left&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="kr"&gt;if&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="o"&gt;%&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt; &lt;span class="kr"&gt;then&lt;/span&gt;
            &lt;span class="n"&gt;x&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;x0&lt;/span&gt;
            &lt;span class="n"&gt;y&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="mi"&gt;8&lt;/span&gt;
        &lt;span class="kr"&gt;else&lt;/span&gt;
            &lt;span class="n"&gt;x&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="n"&gt;dx&lt;/span&gt;
        &lt;span class="kr"&gt;end&lt;/span&gt;
    &lt;span class="kr"&gt;end&lt;/span&gt;
&lt;span class="kr"&gt;end&lt;/span&gt;

&lt;span class="kr"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;_init&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="n"&gt;player&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;anise&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;vec&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;64&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;64&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;span class="kr"&gt;end&lt;/span&gt;

&lt;span class="kr"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;_update&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="n"&gt;t&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
    &lt;span class="n"&gt;t&lt;/span&gt; &lt;span class="o"&gt;%=&lt;/span&gt; &lt;span class="mi"&gt;120&lt;/span&gt;
    &lt;span class="n"&gt;player&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;move&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;vec&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;b2n&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;btn&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="err"&gt;➡️&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;b2n&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;btn&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="err"&gt;⬅️&lt;/span&gt;&lt;span class="p"&gt;)),&lt;/span&gt;
        &lt;span class="n"&gt;b2n&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;btn&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="err"&gt;⬇️&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;b2n&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;btn&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="err"&gt;⬆️&lt;/span&gt;&lt;span class="p"&gt;)))&lt;/span&gt;
    &lt;span class="n"&gt;player&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="n"&gt;update&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="kr"&gt;end&lt;/span&gt;

&lt;span class="kr"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;_draw&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="n"&gt;cls&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="n"&gt;player&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="n"&gt;draw&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="kr"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;p&gt;What a mouthful!  But &lt;em&gt;for the most part&lt;/em&gt;, this is the same code as before, just rearranged.  For example, the new &lt;code&gt;anise:draw()&lt;/code&gt; method has basically been cut and pasted from my old &lt;code&gt;_draw()&lt;/code&gt; — all except the &lt;code&gt;cls()&lt;/code&gt; call, since that has nothing to do with drawing Star&amp;nbsp;Anise.&lt;/p&gt;
&lt;p&gt;I&amp;#8217;ve combined the &lt;code&gt;px&lt;/code&gt; and &lt;code&gt;py&lt;/code&gt; variables into a single vector, &lt;code&gt;pos&lt;/code&gt; (short for &amp;#8220;position&amp;#8221;), which I now have to refer to as &lt;code&gt;self.pos&lt;/code&gt; — that&amp;#8217;s so &lt;span class="caps"&gt;PICO&lt;/span&gt;-8 knows whose &lt;code&gt;pos&lt;/code&gt; I&amp;#8217;m talking about.  After all, it&amp;#8217;s theoretically possible for me to create more than one Star Anise now.  I won&amp;#8217;t, but &lt;span class="caps"&gt;PICO&lt;/span&gt;-8 doesn&amp;#8217;t know&amp;nbsp;that!&lt;/p&gt;
&lt;aside class="aside--tricky-tradeoff"&gt;
&lt;p&gt;A downside of this approach is that it costs more tokens — &lt;code&gt;px&lt;/code&gt; is one token, but &lt;code&gt;self.pos.x&lt;/code&gt; is three (and a few more bytes, too).  With any luck, this extra cost in code size will be balanced out later when I find ways to reuse some of this code.  Some PICO-8 games deliberately sacrifice structure to save&amp;nbsp;tokens.&lt;/p&gt;
&lt;/aside&gt;
&lt;p&gt;A Star Anise object is created and assigned to &lt;code&gt;player&lt;/code&gt; when the game starts, and then &lt;code&gt;_update()&lt;/code&gt; calls &lt;code&gt;player:update()&lt;/code&gt; and &lt;code&gt;_draw()&lt;/code&gt; calls &lt;code&gt;player:draw()&lt;/code&gt; to get the same effects as&amp;nbsp;before.&lt;/p&gt;
&lt;p&gt;I did make one moderately dramatic change in this code.  The wordy code I had for reading buttons has become much more compact and inscrutable, and the &lt;code&gt;moving&lt;/code&gt; variable is gone.  A big part of the reason for this is that I consider Star Anise&amp;#8217;s &lt;em&gt;movement&lt;/em&gt; to be part of himself, but reading input to be part of the &lt;em&gt;game&lt;/em&gt;, so I wanted to split them up.  That means &lt;code&gt;moving&lt;/code&gt; is a bit awkward, since I previously updated it as part of reading input.  Instead, I&amp;#8217;ve turned Star Anise&amp;#8217;s movement into another vector, which I set in &lt;code&gt;_update()&lt;/code&gt; using this&amp;nbsp;mouthful:&lt;/p&gt;
&lt;table class="highlighttable"&gt;&lt;tr&gt;&lt;td class="linenos"&gt;&lt;div class="linenodiv"&gt;&lt;pre&gt;&lt;span class="normal"&gt;1&lt;/span&gt;
&lt;span class="normal"&gt;2&lt;/span&gt;
&lt;span class="normal"&gt;3&lt;/span&gt;
&lt;span class="normal"&gt;4&lt;/span&gt;
&lt;span class="normal"&gt;5&lt;/span&gt;
&lt;span class="normal"&gt;6&lt;/span&gt;
&lt;span class="normal"&gt;7&lt;/span&gt;
&lt;span class="normal"&gt;8&lt;/span&gt;
&lt;span class="normal"&gt;9&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/td&gt;&lt;td class="code"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="c1"&gt;-- top-level&lt;/span&gt;
&lt;span class="kr"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;b2n&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="kr"&gt;return&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt; &lt;span class="ow"&gt;and&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="ow"&gt;or&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;
&lt;span class="kr"&gt;end&lt;/span&gt;

&lt;span class="c1"&gt;-- in _update()&lt;/span&gt;
    &lt;span class="n"&gt;player&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;move&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;vec&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;b2n&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;btn&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="err"&gt;➡️&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;b2n&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;btn&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="err"&gt;⬅️&lt;/span&gt;&lt;span class="p"&gt;)),&lt;/span&gt;
        &lt;span class="n"&gt;b2n&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;btn&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="err"&gt;⬇️&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;b2n&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;btn&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="err"&gt;⬆️&lt;/span&gt;&lt;span class="p"&gt;)))&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;p&gt;The &lt;code&gt;b2n()&lt;/code&gt; function turns a &lt;strong&gt;b&lt;/strong&gt;utton into a &lt;strong&gt;n&lt;/strong&gt;umber, and I only use it here.  It turns &lt;code&gt;true&lt;/code&gt; into 1 and &lt;code&gt;false&lt;/code&gt; into 0.  Think of it as measuring &amp;#8220;how much&amp;#8221; the button is held down, from 0 to 1, except of course there can&amp;#8217;t be any answer in the&amp;nbsp;middle.&lt;/p&gt;
&lt;aside class="aside--look-out"&gt;
&lt;p&gt;In Lua, &lt;code&gt;x = a and b or c&lt;/code&gt; is &lt;em&gt;kind of&lt;/em&gt; like a very terse way of&amp;nbsp;writing:&lt;/p&gt;
&lt;table class="highlighttable"&gt;&lt;tr&gt;&lt;td class="linenos"&gt;&lt;div class="linenodiv"&gt;&lt;pre&gt;&lt;span class="normal"&gt;1&lt;/span&gt;
&lt;span class="normal"&gt;2&lt;/span&gt;
&lt;span class="normal"&gt;3&lt;/span&gt;
&lt;span class="normal"&gt;4&lt;/span&gt;
&lt;span class="normal"&gt;5&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/td&gt;&lt;td class="code"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="kr"&gt;if&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt; &lt;span class="kr"&gt;then&lt;/span&gt;
    &lt;span class="n"&gt;x&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt;
&lt;span class="kr"&gt;else&lt;/span&gt;
    &lt;span class="n"&gt;x&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;c&lt;/span&gt;
&lt;span class="kr"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;p&gt;It's a clever abuse of &lt;code&gt;and&lt;/code&gt; and &lt;code&gt;or&lt;/code&gt; that lets you express something like an &lt;code&gt;if&lt;/code&gt;, except producing a value instead of executing code.  It also has a serious pitfall: if &lt;code&gt;b&lt;/code&gt; is a falsy value (so, &lt;code&gt;false&lt;/code&gt; or &lt;code&gt;nul&lt;/code&gt;), the result will &lt;em&gt;always&lt;/em&gt; be &lt;code&gt;c&lt;/code&gt;!  For this reason, I &lt;strong&gt;strongly&lt;/strong&gt; recommend against doing this (in &lt;em&gt;any&lt;/em&gt; language) — I just really, really wanted &lt;code&gt;b2n()&lt;/code&gt; to take up as few tokens as&amp;nbsp;possible.&lt;/p&gt;
&lt;/aside&gt;
&lt;p&gt;Unpacking that a bit further, &lt;code&gt;b2n(btn(➡️)) - b2n(btn(⬅️))&lt;/code&gt; means &amp;#8220;how much we&amp;#8217;re holding right, minus how much we&amp;#8217;re holding left&amp;#8221;.  If the player is only holding the right button, that&amp;#8217;s 1 - 0 = 1.  If they&amp;#8217;re only holding the left button, that&amp;#8217;s 0 - 1 = -1.  If they&amp;#8217;re holding both or neither, that&amp;#8217;s 0.  The results are the same as before, but the code is&amp;nbsp;smaller.&lt;/p&gt;
&lt;aside class="aside--note-from-future"&gt;
&lt;p&gt;This is unreadable garbage that barely saves any tokens, and I will eventually succumb to shame and scrap it.  &lt;em&gt;Especially&lt;/em&gt; since I don't need vertical&amp;nbsp;movement!&lt;/p&gt;
&lt;/aside&gt;
&lt;p&gt;Once Star Anise&amp;#8217;s &lt;code&gt;move&lt;/code&gt; is set, the rest works similarly to before: I update &lt;code&gt;left&lt;/code&gt; based on horizontal movement (but leave it alone when there isn&amp;#8217;t anyway), I alter his position (now using &lt;code&gt;:iadd()&lt;/code&gt;), and I use the walk animation when he&amp;#8217;s moving at all.  And that&amp;#8217;s&amp;nbsp;it!&lt;/p&gt;
&lt;h2 id="from-one-to-many"&gt;&lt;a class="toclink" href="#from-one-to-many"&gt;From one to&amp;nbsp;many&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;I like to use the term &amp;#8220;actor&amp;#8221; to refer to a distinct &lt;em&gt;thing&lt;/em&gt; in the game world; it conjures a charming and concrete image of various characters performing on a stage.  I think I picked it up from the Doom source code.  &amp;#8220;Entity&amp;#8221; is more common and is used heavily in Unity, but can be confused with an &amp;#8220;entity–component–system&amp;#8221; setup, which Unity &lt;em&gt;also&lt;/em&gt; supports.  And then there are heretics who refer to game things as &amp;#8220;objects&amp;#8221; even though that&amp;#8217;s also a programming&amp;nbsp;term.&lt;/p&gt;
&lt;p&gt;This code is a fine start, but it&amp;#8217;s not quite what I want.  There&amp;#8217;s nothing here actually called an actor, for starters.  My setup still only works for Star&amp;nbsp;Anise!&lt;/p&gt;
&lt;p&gt;I&amp;#8217;d better fix that.  The notion of an &amp;#8220;actor&amp;#8221; is pretty vague, so a generic actor won&amp;#8217;t do much by itself, but it&amp;#8217;s nice to define one as a template for how I expect real actors to&amp;nbsp;work.&lt;/p&gt;
&lt;table class="highlighttable"&gt;&lt;tr&gt;&lt;td class="linenos"&gt;&lt;div class="linenodiv"&gt;&lt;pre&gt;&lt;span class="normal"&gt; 1&lt;/span&gt;
&lt;span class="normal"&gt; 2&lt;/span&gt;
&lt;span class="normal"&gt; 3&lt;/span&gt;
&lt;span class="normal"&gt; 4&lt;/span&gt;
&lt;span class="normal"&gt; 5&lt;/span&gt;
&lt;span class="normal"&gt; 6&lt;/span&gt;
&lt;span class="normal"&gt; 7&lt;/span&gt;
&lt;span class="normal"&gt; 8&lt;/span&gt;
&lt;span class="normal"&gt; 9&lt;/span&gt;
&lt;span class="normal"&gt;10&lt;/span&gt;
&lt;span class="normal"&gt;11&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/td&gt;&lt;td class="code"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="kd"&gt;local&lt;/span&gt; &lt;span class="n"&gt;actor&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;obj&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="n"&gt;extend&lt;/span&gt;&lt;span class="p"&gt;{}&lt;/span&gt;

&lt;span class="kr"&gt;function&lt;/span&gt; &lt;span class="nc"&gt;actor&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="nf"&gt;init&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;pos&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;pos&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;pos&lt;/span&gt;
&lt;span class="kr"&gt;end&lt;/span&gt;

&lt;span class="kr"&gt;function&lt;/span&gt; &lt;span class="nc"&gt;actor&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="nf"&gt;update&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="kr"&gt;end&lt;/span&gt;

&lt;span class="kr"&gt;function&lt;/span&gt; &lt;span class="nc"&gt;actor&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="nf"&gt;draw&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="kr"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;p&gt;How does a blank actor update or draw itself?  By doing&amp;nbsp;nothing.&lt;/p&gt;
&lt;p&gt;(I do assume that every actor has a position; this may not necessarily be the case in games with very broad ideas about what an &amp;#8220;actor&amp;#8221; is, but it&amp;#8217;s reasonable enough for my&amp;nbsp;purposes.)&lt;/p&gt;
&lt;p&gt;Now, to link this with Star Anise, I&amp;#8217;ll have &lt;code&gt;anise&lt;/code&gt; &lt;em&gt;inherit&lt;/em&gt; from &lt;code&gt;actor&lt;/code&gt;.  That means he&amp;#8217;ll become a specialized kind of &lt;code&gt;actor&lt;/code&gt;, and in particular, all the methods on &lt;code&gt;actor&lt;/code&gt; will also appear on &lt;code&gt;anise&lt;/code&gt;.  You may notice that &lt;code&gt;anise&lt;/code&gt; was previously a specialized kind of &lt;code&gt;obj&lt;/code&gt; (like &lt;code&gt;actor&lt;/code&gt; and &lt;code&gt;vec&lt;/code&gt;) — in fact, the only reason I can call &lt;code&gt;vec(x, y)&lt;/code&gt; like a function is that it inherits some magic stuff from &lt;code&gt;obj&lt;/code&gt;.&amp;nbsp;Surprise!&lt;/p&gt;
&lt;table class="highlighttable"&gt;&lt;tr&gt;&lt;td class="linenos"&gt;&lt;div class="linenodiv"&gt;&lt;pre&gt;&lt;span class="normal"&gt;1&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/td&gt;&lt;td class="code"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="kd"&gt;local&lt;/span&gt; &lt;span class="n"&gt;anise&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;actor&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="n"&gt;extend&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;p&gt;I can now delete &lt;code&gt;anise:init()&lt;/code&gt;, since it&amp;#8217;s identical to &lt;code&gt;actor:init()&lt;/code&gt;.  I still have &lt;code&gt;anise:update()&lt;/code&gt; and &lt;code&gt;anise:draw()&lt;/code&gt;, which override the methods on &lt;code&gt;actor&lt;/code&gt;, so those don&amp;#8217;t need&amp;nbsp;changing.&lt;/p&gt;
&lt;p&gt;Everything &lt;em&gt;still&lt;/em&gt; only works for Star Anise, but I&amp;#8217;m getting closer!  I only need one more change.  Instead of having only &lt;code&gt;player&lt;/code&gt;, I will make a &lt;em&gt;list&lt;/em&gt; of&amp;nbsp;actors.&lt;/p&gt;
&lt;aside class="aside--well-actually"&gt;
&lt;p&gt;The Lua structure I'm using here is called a &lt;em&gt;table&lt;/em&gt;; strictly speaking, there's nothing in Lua called a &lt;em&gt;list&lt;/em&gt;.  But tables are used multiple ways in Lua, so I'm going to call one a list when it's intended as...  well, as a list of things.  Intent is&amp;nbsp;important!&lt;/p&gt;
&lt;p&gt;Oh, and some programmers might be confused about "list" because they assume it means a &lt;em&gt;linked list&lt;/em&gt;, which is a very different thing entirely, and they would call this an &lt;em&gt;array&lt;/em&gt;.  Or maybe even a &lt;em&gt;vector&lt;/em&gt;.  I told you, we are really quite bad at naming&amp;nbsp;things.&lt;/p&gt;
&lt;/aside&gt;
&lt;table class="highlighttable"&gt;&lt;tr&gt;&lt;td class="linenos"&gt;&lt;div class="linenodiv"&gt;&lt;pre&gt;&lt;span class="normal"&gt; 1&lt;/span&gt;
&lt;span class="normal"&gt; 2&lt;/span&gt;
&lt;span class="normal"&gt; 3&lt;/span&gt;
&lt;span class="normal"&gt; 4&lt;/span&gt;
&lt;span class="normal"&gt; 5&lt;/span&gt;
&lt;span class="normal"&gt; 6&lt;/span&gt;
&lt;span class="normal"&gt; 7&lt;/span&gt;
&lt;span class="normal"&gt; 8&lt;/span&gt;
&lt;span class="normal"&gt; 9&lt;/span&gt;
&lt;span class="normal"&gt;10&lt;/span&gt;
&lt;span class="normal"&gt;11&lt;/span&gt;
&lt;span class="normal"&gt;12&lt;/span&gt;
&lt;span class="normal"&gt;13&lt;/span&gt;
&lt;span class="normal"&gt;14&lt;/span&gt;
&lt;span class="normal"&gt;15&lt;/span&gt;
&lt;span class="normal"&gt;16&lt;/span&gt;
&lt;span class="normal"&gt;17&lt;/span&gt;
&lt;span class="normal"&gt;18&lt;/span&gt;
&lt;span class="normal"&gt;19&lt;/span&gt;
&lt;span class="normal"&gt;20&lt;/span&gt;
&lt;span class="normal"&gt;21&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/td&gt;&lt;td class="code"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="c1"&gt;-- at the top&lt;/span&gt;
&lt;span class="kd"&gt;local&lt;/span&gt; &lt;span class="n"&gt;actors&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{}&lt;/span&gt;

&lt;span class="kr"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;_init&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="n"&gt;player&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;anise&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;vec&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;64&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;64&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
    &lt;span class="n"&gt;add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;actors&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;player&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="kr"&gt;end&lt;/span&gt;

&lt;span class="kr"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;_update&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="c1"&gt;-- ...mostly same as before...&lt;/span&gt;
    &lt;span class="kr"&gt;for&lt;/span&gt; &lt;span class="n"&gt;actor&lt;/span&gt; &lt;span class="kr"&gt;in&lt;/span&gt; &lt;span class="n"&gt;all&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;actors&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="kr"&gt;do&lt;/span&gt;
        &lt;span class="n"&gt;actor&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="n"&gt;update&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="kr"&gt;end&lt;/span&gt;
&lt;span class="kr"&gt;end&lt;/span&gt;

&lt;span class="kr"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;_draw&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="n"&gt;cls&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="kr"&gt;for&lt;/span&gt; &lt;span class="n"&gt;actor&lt;/span&gt; &lt;span class="kr"&gt;in&lt;/span&gt; &lt;span class="n"&gt;all&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;actors&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="kr"&gt;do&lt;/span&gt;
        &lt;span class="n"&gt;actor&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="n"&gt;draw&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="kr"&gt;end&lt;/span&gt;
&lt;span class="kr"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;p&gt;This does pretty much what it reads like.  The &lt;code&gt;add()&lt;/code&gt; function, specific to &lt;span class="caps"&gt;PICO&lt;/span&gt;-8, adds an item to the end of a list.  The &lt;code&gt;all()&lt;/code&gt; function, also specific to &lt;span class="caps"&gt;PICO&lt;/span&gt;-8, helps go through a list.  And the &lt;code&gt;for&lt;/code&gt; blocks mean, for each thing in this list, run this&amp;nbsp;code.&lt;/p&gt;
&lt;p&gt;Now, at last, I have something that could work for actors other than Star Anise.  All I need to do is define them and add them to the &lt;code&gt;actors&lt;/code&gt; list, and they&amp;#8217;ll automatically be updated and drawn, just like&amp;nbsp;him!&lt;/p&gt;
&lt;aside class="aside--look-out"&gt;
&lt;p&gt;I have done a slightly naughty thing here.  I used &lt;code&gt;actor&lt;/code&gt; as the name of a &lt;em&gt;type&lt;/em&gt;, a generic actor with no particular behavior, but I also used it in those &lt;code&gt;for&lt;/code&gt; loops as the name of a &lt;em&gt;specific actor&lt;/em&gt;.  This is generally something to avoid — at best it can confuse a reader, at worst you find yourself wanting to use both things at the same time.  Like I said before, I would usually name types like &lt;code&gt;Actor&lt;/code&gt;, but the PICO-8 prevents&amp;nbsp;it.&lt;/p&gt;
&lt;p&gt;Fear not, though: Lua's &lt;code&gt;for&lt;/code&gt; statement makes a &lt;em&gt;new&lt;/em&gt; variable called &lt;code&gt;actor&lt;/code&gt; that hides the outer one, and then forgets about it at the end of the block, leaving the original safely&amp;nbsp;untouched.&lt;/p&gt;
&lt;/aside&gt;
&lt;p&gt;Admittedly, this hasn&amp;#8217;t gotten me anywhere concrete.  The game still plays exactly the same as it did when I started.  I&amp;#8217;m betting that I&amp;#8217;ll eventually have more than one actor, though, so I might as well lay the groundwork for that now while it&amp;#8217;s easy.  It doesn&amp;#8217;t take much effort, and I find that if I give myself little early inroads like this, it feels like less of a slog to later come back and expand on the ideas.  This is the sort of thing I meant by more structure revealing itself — once I have &lt;em&gt;one&lt;/em&gt; actor, a natural next step is to allow for &lt;em&gt;several&lt;/em&gt;&amp;nbsp;actors.&lt;/p&gt;
&lt;h2 id="preparing-for-collision-detection"&gt;&lt;a class="toclink" href="#preparing-for-collision-detection"&gt;Preparing for collision&amp;nbsp;detection&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;I&amp;#8217;ve put it off long enough.  I can&amp;#8217;t avoid it any longer.  But it&amp;#8217;s complicated enough to deserve its own post, so I don&amp;#8217;t quite want to do it&amp;nbsp;yet.&lt;/p&gt;
&lt;p&gt;Instead, I&amp;#8217;ll write as much code as possible &lt;em&gt;except for&lt;/em&gt; the actual collision detection.  There&amp;#8217;s a bit more work to do to plug it&amp;nbsp;in.&lt;/p&gt;
&lt;p&gt;For example: what am I going to collide &lt;em&gt;with&lt;/em&gt;?  The only thing in the universe, currently, is Star Anise himself.  It would be nice to have, say, some ground.  And that&amp;#8217;s a great excuse to toodle around a bit in the sprite&amp;nbsp;editor.&lt;/p&gt;
&lt;div class="prose-full-illustration"&gt;
&lt;img src="https://eev.ee/media/gamedev-from-scratch/v1-ground.png" alt="A set of simple ground tiles, drawn in the PICO-8 sprite editor"&gt;
&lt;/div&gt;

&lt;p&gt;I went through several iterations before landing on this.  Star Anise lives on a moon, so that was my guiding principle.  The moon is gray and dusty and pitted, so at first I tried drawing a tile with tiny craters in it.  Unfortunately, that was a busy mess to look at when tiled, and I didn&amp;#8217;t think I&amp;#8217;d have enough tile space for having different variants of tiles.  I&amp;#8217;m already using 9 tiles here just to have neat&amp;nbsp;edges.&lt;/p&gt;
&lt;p&gt;And so I landed on this simple pattern with just enough texture to be reminiscent of &lt;em&gt;something&lt;/em&gt;, which is all you really need with low-res sprite art.  It worked out well enough to survive, nearly unchanged, all the way to the final game.  It was inspired by a vague memory of Starbound&amp;#8217;s &lt;a href="https://starbounder.org/Moondust"&gt;moondust&lt;/a&gt; tiles, which I was pretty sure had diagonal striping, though I didn&amp;#8217;t actually look at them to be&amp;nbsp;sure.&lt;/p&gt;
&lt;p&gt;You may notice I drew these on the second tab of sprites.  I want to be able to find tiles quickly when drawing maps, so I thought I&amp;#8217;d put &amp;#8220;terrain&amp;#8221; on a dedicated tab and reserve the first one for Star Anise, other actors, special effects, and other less-common tiles.  That turned out to be a good&amp;nbsp;idea.&lt;/p&gt;
&lt;p&gt;You may &lt;em&gt;also&lt;/em&gt; notice that one of those dots on the middle right is lit up.  How mysterious!  We&amp;#8217;ll get to that next&amp;nbsp;time.&lt;/p&gt;
&lt;p&gt;With a few simple tiles drawn, I can sprinkle a couple in the map tab.  I know I want Metroid-style discrete screens, so I&amp;#8217;m not worried about camera scrolling yet; the top-left corner (16×16 tiles) is enough to play with for&amp;nbsp;now.&lt;/p&gt;
&lt;p&gt;I draw two rows of tiles at the bottom of that screen.  It&amp;#8217;s a little hard to gauge since the toolbar and status bar get in the way, but the bottom row of the screen will be at y = 15.  You can also hold &lt;kbd&gt;Spacebar&lt;/kbd&gt; to get a grid, with squares indicating every&amp;nbsp;half-screen.&lt;/p&gt;
&lt;div class="prose-full-illustration"&gt;
&lt;img src="https://eev.ee/media/gamedev-from-scratch/v1-map.png" alt="PICO-8's map editor, showing two rows of moon tiles"&gt;
&lt;/div&gt;

&lt;p&gt;Finally, to make this appear in the game, I need only ask &lt;span class="caps"&gt;PICO&lt;/span&gt;-8 to draw the map before I draw actors on top of&amp;nbsp;it.&lt;/p&gt;
&lt;table class="highlighttable"&gt;&lt;tr&gt;&lt;td class="linenos"&gt;&lt;div class="linenodiv"&gt;&lt;pre&gt;&lt;span class="normal"&gt;1&lt;/span&gt;
&lt;span class="normal"&gt;2&lt;/span&gt;
&lt;span class="normal"&gt;3&lt;/span&gt;
&lt;span class="normal"&gt;4&lt;/span&gt;
&lt;span class="normal"&gt;5&lt;/span&gt;
&lt;span class="normal"&gt;6&lt;/span&gt;
&lt;span class="normal"&gt;7&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/td&gt;&lt;td class="code"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="kr"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;_draw&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="n"&gt;cls&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="n"&gt;map&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;32&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;32&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="kr"&gt;for&lt;/span&gt; &lt;span class="n"&gt;actor&lt;/span&gt; &lt;span class="kr"&gt;in&lt;/span&gt; &lt;span class="n"&gt;all&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;actors&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="kr"&gt;do&lt;/span&gt;
        &lt;span class="n"&gt;actor&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="n"&gt;draw&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="kr"&gt;end&lt;/span&gt;
&lt;span class="kr"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;p&gt;The &lt;span class="caps"&gt;PICO&lt;/span&gt;-8 &lt;code&gt;map()&lt;/code&gt; function takes (at least) six arguments: the top-left corner of the map to start drawing from, measured in tiles; the top-left corner on the screen to draw to, measured in pixels; and the width/height of the rectangle to draw from the map, measured in tiles.  This will draw a 32×32 block of tiles from the top-left corner of the map to the top-left corner of the&amp;nbsp;screen.&lt;/p&gt;
&lt;aside class="aside--note-from-future"&gt;
&lt;p&gt;I'm not sure why I used 32×32 here, when the screen is only 16×16 tiles&amp;nbsp;big!&lt;/p&gt;
&lt;/aside&gt;
&lt;p&gt;Of course, with no collision detection, those tiles are nothing more than background pixels, and the game treats them as&amp;nbsp;such.&lt;/p&gt;
&lt;div class="prose-full-illustration"&gt;
&lt;img src="https://eev.ee/media/gamedev-from-scratch/v1-overlap.png" alt="Star Anise standing in front of the moon tiles"&gt;
&lt;/div&gt;

&lt;p&gt;No problem.  I can fix that.  Sort&amp;nbsp;of.&lt;/p&gt;
&lt;h2 id="not-quite-collision-detection"&gt;&lt;a class="toclink" href="#not-quite-collision-detection"&gt;Not quite collision&amp;nbsp;detection&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;I&amp;#8217;m not going into collision detection yet, but I can give you a &lt;em&gt;taste&lt;/em&gt;, to give you an idea of the&amp;nbsp;goals.&lt;/p&gt;
&lt;p&gt;The core of it comes down to this line, from the end of &lt;code&gt;anise:update()&lt;/code&gt;.&lt;/p&gt;
&lt;table class="highlighttable"&gt;&lt;tr&gt;&lt;td class="linenos"&gt;&lt;div class="linenodiv"&gt;&lt;pre&gt;&lt;span class="normal"&gt;1&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/td&gt;&lt;td class="code"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;    &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;pos&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="n"&gt;iadd&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;move&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;p&gt;That moves Star Anise by one pixel in each direction the player is holding.  What I want to do is &lt;em&gt;stop him&lt;/em&gt; when he hits something&amp;nbsp;solid.&lt;/p&gt;
&lt;p&gt;Hm, sounds hard.  Let&amp;#8217;s think for a moment about a simpler problem: how can I stop him falling through the ground, in the dumbest way&amp;nbsp;possible?&lt;/p&gt;
&lt;p&gt;The ground is flat, and it takes up the bottow two rows of tiles.  That means its top edge is 14 tiles, or 112 pixels, below the top of the screen.  Thus, Star Anise should not be able to move below that&amp;nbsp;line.&lt;/p&gt;
&lt;p&gt;But wait!  Star Anise&amp;#8217;s &lt;em&gt;position&lt;/em&gt; is a single point at his top left, not even inside his helmet.  What I really want is for his &lt;em&gt;feet&lt;/em&gt; to not pass below that line, and the bottom of his feet is three tiles (24 pixels) below his position.  Thus, his position should not pass below y = 112 - 24 =&amp;nbsp;88.&lt;/p&gt;
&lt;p&gt;That sounds&amp;nbsp;doable.&lt;/p&gt;
&lt;table class="highlighttable"&gt;&lt;tr&gt;&lt;td class="linenos"&gt;&lt;div class="linenodiv"&gt;&lt;pre&gt;&lt;span class="normal"&gt;1&lt;/span&gt;
&lt;span class="normal"&gt;2&lt;/span&gt;
&lt;span class="normal"&gt;3&lt;/span&gt;
&lt;span class="normal"&gt;4&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/td&gt;&lt;td class="code"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;    &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;pos&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="n"&gt;iadd&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;move&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="kr"&gt;if&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;pos&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;y&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;88&lt;/span&gt; &lt;span class="kr"&gt;then&lt;/span&gt;
        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;pos&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;y&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;88&lt;/span&gt;
    &lt;span class="kr"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;p&gt;And sure enough, it&amp;nbsp;works!&lt;/p&gt;
&lt;div class="prose-full-illustration"&gt;
&lt;img src="https://eev.ee/media/gamedev-from-scratch/v1-collision-taste.gif" alt="Star Anise walking through the air, but not through the floor"&gt;
&lt;/div&gt;

&lt;p&gt;This isn&amp;#8217;t going to get us very far, of course.  He still walks through the air, he can still walk off the screen, and if I change the terrain then the code won&amp;#8217;t be right any more.  I&amp;#8217;m also pretty sure I didn&amp;#8217;t actually write this in practice.  But hopefully it gives you the teeniest idea of the problem we&amp;#8217;re going to solve next&amp;nbsp;time.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Part 2: Collision →&lt;/strong&gt; (coming&amp;nbsp;soon!)&lt;/p&gt;
&lt;h2 id="appendix-the-lua-object-model"&gt;&lt;a class="toclink" href="#appendix-the-lua-object-model"&gt;Appendix: the Lua object&amp;nbsp;model&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Really, really, &lt;em&gt;really&lt;/em&gt; quickly, here&amp;#8217;s how that &lt;code&gt;obj&lt;/code&gt; snippet&amp;nbsp;works.&lt;/p&gt;
&lt;p&gt;Lua&amp;#8217;s primary data structure is the &lt;em&gt;table&lt;/em&gt;.  It can be used to make ordered lists of things, as I did above with &lt;code&gt;actors&lt;/code&gt;, but it can also be used for arbitrary mappings.  I can assign some value to a particular &lt;em&gt;key&lt;/em&gt;, then quickly look that key up again later.  Kind of like a&amp;nbsp;Rolodex.&lt;/p&gt;
&lt;table class="highlighttable"&gt;&lt;tr&gt;&lt;td class="linenos"&gt;&lt;div class="linenodiv"&gt;&lt;pre&gt;&lt;span class="normal"&gt;1&lt;/span&gt;
&lt;span class="normal"&gt;2&lt;/span&gt;
&lt;span class="normal"&gt;3&lt;/span&gt;
&lt;span class="normal"&gt;4&lt;/span&gt;
&lt;span class="normal"&gt;5&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/td&gt;&lt;td class="code"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="kd"&gt;local&lt;/span&gt; &lt;span class="n"&gt;lunekos&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;anise&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;star anise is the best&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;purrl&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;purrl is very lovely&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;lunekos&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;anise&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;p&gt;Note that the values (and keys!) don&amp;#8217;t have to be strings; they can be anything you like, even other tables.  But for string keys, you can do something&amp;nbsp;special:&lt;/p&gt;
&lt;table class="highlighttable"&gt;&lt;tr&gt;&lt;td class="linenos"&gt;&lt;div class="linenodiv"&gt;&lt;pre&gt;&lt;span class="normal"&gt;1&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/td&gt;&lt;td class="code"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;lunekos&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;anise&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;  &lt;span class="c1"&gt;-- same as above&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;p&gt;&lt;em&gt;Everywhere&lt;/em&gt; you see a dot (or colon) used in Lua, that&amp;#8217;s actually looking up a string in a&amp;nbsp;table.&lt;/p&gt;
&lt;p&gt;With me so far?  Hope&amp;nbsp;so.&lt;/p&gt;
&lt;p&gt;Any Lua table can also be assigned a &lt;em&gt;metatable&lt;/em&gt;, which is another table full of various magic stuff that affects the first table&amp;#8217;s behavior.  Most of the magic stuff takes the form of a special key, starting with two underscores, whose value is a function that will be called in particular circumstances.  That function is then called a &lt;em&gt;metamethod&lt;/em&gt;.  (There&amp;#8217;s a &lt;a href="https://www.lua.org/pil/13.html"&gt;whole section on this in the Lua book&lt;/a&gt;, and &lt;a href="http://lua-users.org/wiki/MetatableEvents"&gt;a summary of metamethods on the Lua wiki&lt;/a&gt;.)&lt;/p&gt;
&lt;p&gt;One common use for metamethods is to make normal Lua operators work on tables.  For example, you can make a table that can be called like a function by providing the &lt;code&gt;__call&lt;/code&gt; metamethod.&lt;/p&gt;
&lt;table class="highlighttable"&gt;&lt;tr&gt;&lt;td class="linenos"&gt;&lt;div class="linenodiv"&gt;&lt;pre&gt;&lt;span class="normal"&gt; 1&lt;/span&gt;
&lt;span class="normal"&gt; 2&lt;/span&gt;
&lt;span class="normal"&gt; 3&lt;/span&gt;
&lt;span class="normal"&gt; 4&lt;/span&gt;
&lt;span class="normal"&gt; 5&lt;/span&gt;
&lt;span class="normal"&gt; 6&lt;/span&gt;
&lt;span class="normal"&gt; 7&lt;/span&gt;
&lt;span class="normal"&gt; 8&lt;/span&gt;
&lt;span class="normal"&gt; 9&lt;/span&gt;
&lt;span class="normal"&gt;10&lt;/span&gt;
&lt;span class="normal"&gt;11&lt;/span&gt;
&lt;span class="normal"&gt;12&lt;/span&gt;
&lt;span class="normal"&gt;13&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/td&gt;&lt;td class="code"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="kd"&gt;local&lt;/span&gt; &lt;span class="n"&gt;t&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;stuff&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;5678&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="kd"&gt;local&lt;/span&gt; &lt;span class="n"&gt;meta&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;-- this is just a regular table key with a function for its value&lt;/span&gt;
    &lt;span class="n"&gt;__call&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kr"&gt;function&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;tbl&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;my stuff is&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;tbl&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;stuff&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
    &lt;span class="kr"&gt;end&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="nb"&gt;setmetatable&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;meta&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;  &lt;span class="c1"&gt;-- my stuff is 5678&lt;/span&gt;
&lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;stuff&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;yoinky&amp;quot;&lt;/span&gt;
&lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;  &lt;span class="c1"&gt;-- my stuff is yoinky&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;p&gt;One especially useful metamethod is &lt;code&gt;__index&lt;/code&gt;, which is called when you try to read a key from the table, but the key doesn&amp;#8217;t&amp;nbsp;exist.&lt;/p&gt;
&lt;table class="highlighttable"&gt;&lt;tr&gt;&lt;td class="linenos"&gt;&lt;div class="linenodiv"&gt;&lt;pre&gt;&lt;span class="normal"&gt; 1&lt;/span&gt;
&lt;span class="normal"&gt; 2&lt;/span&gt;
&lt;span class="normal"&gt; 3&lt;/span&gt;
&lt;span class="normal"&gt; 4&lt;/span&gt;
&lt;span class="normal"&gt; 5&lt;/span&gt;
&lt;span class="normal"&gt; 6&lt;/span&gt;
&lt;span class="normal"&gt; 7&lt;/span&gt;
&lt;span class="normal"&gt; 8&lt;/span&gt;
&lt;span class="normal"&gt; 9&lt;/span&gt;
&lt;span class="normal"&gt;10&lt;/span&gt;
&lt;span class="normal"&gt;11&lt;/span&gt;
&lt;span class="normal"&gt;12&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/td&gt;&lt;td class="code"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="kd"&gt;local&lt;/span&gt; &lt;span class="n"&gt;counts&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;apples&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;bananas&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="nb"&gt;setmetatable&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;counts&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;__index&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kr"&gt;function&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;tbl&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="kr"&gt;return&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;
    &lt;span class="kr"&gt;end&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;
&lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;counts&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;bananas&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;  &lt;span class="c1"&gt;-- 3&lt;/span&gt;
&lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;counts&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;mangoes&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;  &lt;span class="c1"&gt;-- 0&lt;/span&gt;
&lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;counts&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;apples&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;  &lt;span class="c1"&gt;-- 5&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;p&gt;Instead of a function, &lt;code&gt;__index&lt;/code&gt; can also be &lt;em&gt;another&lt;/em&gt; (third!) table, in which case the key will be looked up in &lt;em&gt;that&lt;/em&gt; table instead.  And if that table has a metatable with an &lt;code&gt;__index&lt;/code&gt;, Lua will follow that too, and keep on going until it gets an&amp;nbsp;answer.&lt;/p&gt;
&lt;p&gt;This is essentially what&amp;#8217;s called &lt;em&gt;prototypical inheritance&lt;/em&gt;, as seen in JavaScript (and more subtly in Python): an object consists of its own values plus a &lt;em&gt;prototype&lt;/em&gt;, and if code tries to fetch something from the object that doesn&amp;#8217;t exist, the prototype is checked instead.  Since the prototype might have its own prototype, the whole sequence is called the &lt;em&gt;prototype chain&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;That&amp;#8217;s all you need to know to follow the &lt;code&gt;obj&lt;/code&gt; snippet, so here it is&amp;nbsp;again.&lt;/p&gt;
&lt;table class="highlighttable"&gt;&lt;tr&gt;&lt;td class="linenos"&gt;&lt;div class="linenodiv"&gt;&lt;pre&gt;&lt;span class="normal"&gt; 1&lt;/span&gt;
&lt;span class="normal"&gt; 2&lt;/span&gt;
&lt;span class="normal"&gt; 3&lt;/span&gt;
&lt;span class="normal"&gt; 4&lt;/span&gt;
&lt;span class="normal"&gt; 5&lt;/span&gt;
&lt;span class="normal"&gt; 6&lt;/span&gt;
&lt;span class="normal"&gt; 7&lt;/span&gt;
&lt;span class="normal"&gt; 8&lt;/span&gt;
&lt;span class="normal"&gt; 9&lt;/span&gt;
&lt;span class="normal"&gt;10&lt;/span&gt;
&lt;span class="normal"&gt;11&lt;/span&gt;
&lt;span class="normal"&gt;12&lt;/span&gt;
&lt;span class="normal"&gt;13&lt;/span&gt;
&lt;span class="normal"&gt;14&lt;/span&gt;
&lt;span class="normal"&gt;15&lt;/span&gt;
&lt;span class="normal"&gt;16&lt;/span&gt;
&lt;span class="normal"&gt;17&lt;/span&gt;
&lt;span class="normal"&gt;18&lt;/span&gt;
&lt;span class="normal"&gt;19&lt;/span&gt;
&lt;span class="normal"&gt;20&lt;/span&gt;
&lt;span class="normal"&gt;21&lt;/span&gt;
&lt;span class="normal"&gt;22&lt;/span&gt;
&lt;span class="normal"&gt;23&lt;/span&gt;
&lt;span class="normal"&gt;24&lt;/span&gt;
&lt;span class="normal"&gt;25&lt;/span&gt;
&lt;span class="normal"&gt;26&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/td&gt;&lt;td class="code"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="kr"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;nop&lt;/span&gt;&lt;span class="p"&gt;(...)&lt;/span&gt; &lt;span class="kr"&gt;return&lt;/span&gt; &lt;span class="p"&gt;...&lt;/span&gt; &lt;span class="kr"&gt;end&lt;/span&gt;

&lt;span class="kd"&gt;local&lt;/span&gt; &lt;span class="n"&gt;obj&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;init&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;nop&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="n"&gt;obj&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;__index&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;obj&lt;/span&gt;

&lt;span class="kr"&gt;function&lt;/span&gt; &lt;span class="nc"&gt;obj&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="nf"&gt;__call&lt;/span&gt;&lt;span class="p"&gt;(...)&lt;/span&gt;
    &lt;span class="kd"&gt;local&lt;/span&gt; &lt;span class="n"&gt;o&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;setmetatable&lt;/span&gt;&lt;span class="p"&gt;({},&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="kr"&gt;return&lt;/span&gt; &lt;span class="n"&gt;o&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;o&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="n"&gt;init&lt;/span&gt;&lt;span class="p"&gt;(...)&lt;/span&gt;
&lt;span class="kr"&gt;end&lt;/span&gt;

&lt;span class="c1"&gt;-- subclassing&lt;/span&gt;
&lt;span class="kr"&gt;function&lt;/span&gt; &lt;span class="nc"&gt;obj&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="nf"&gt;extend&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;proto&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;proto&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;proto&lt;/span&gt; &lt;span class="ow"&gt;or&lt;/span&gt; &lt;span class="p"&gt;{}&lt;/span&gt;

    &lt;span class="c1"&gt;-- copy meta values, since lua doesn&amp;#39;t walk the prototype chain to find them&lt;/span&gt;
    &lt;span class="kr"&gt;for&lt;/span&gt; &lt;span class="n"&gt;k&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;v&lt;/span&gt; &lt;span class="kr"&gt;in&lt;/span&gt; &lt;span class="nb"&gt;pairs&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="kr"&gt;do&lt;/span&gt;
        &lt;span class="kr"&gt;if&lt;/span&gt; &lt;span class="n"&gt;sub&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;k&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;__&amp;quot;&lt;/span&gt; &lt;span class="kr"&gt;then&lt;/span&gt;
            &lt;span class="n"&gt;proto&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;k&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;v&lt;/span&gt;
        &lt;span class="kr"&gt;end&lt;/span&gt;
    &lt;span class="kr"&gt;end&lt;/span&gt;

    &lt;span class="n"&gt;proto&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;__index&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;proto&lt;/span&gt;
    &lt;span class="n"&gt;proto&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;__super&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;

    &lt;span class="kr"&gt;return&lt;/span&gt; &lt;span class="nb"&gt;setmetatable&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;proto&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="kr"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;p&gt;The idea is that types are used both as metatables &lt;em&gt;and&lt;/em&gt; prototypes — they are always their own &lt;code&gt;__index&lt;/code&gt;.  At first, we have only &lt;code&gt;obj&lt;/code&gt;, which looks like&amp;nbsp;this:&lt;/p&gt;
&lt;table class="highlighttable"&gt;&lt;tr&gt;&lt;td class="linenos"&gt;&lt;div class="linenodiv"&gt;&lt;pre&gt;&lt;span class="normal"&gt;1&lt;/span&gt;
&lt;span class="normal"&gt;2&lt;/span&gt;
&lt;span class="normal"&gt;3&lt;/span&gt;
&lt;span class="normal"&gt;4&lt;/span&gt;
&lt;span class="normal"&gt;5&lt;/span&gt;
&lt;span class="normal"&gt;6&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/td&gt;&lt;td class="code"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="kd"&gt;local&lt;/span&gt; &lt;span class="n"&gt;obj&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;init&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;nop&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;__index&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;obj&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;__call&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kr"&gt;function&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;...&lt;/span&gt; &lt;span class="kr"&gt;end&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;extend&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kr"&gt;function&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;...&lt;/span&gt; &lt;span class="kr"&gt;end&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;p&gt;Now we use &lt;code&gt;obj:extend{}&lt;/code&gt; to create a new type.  Follow along and see what happens.  Lua only looks for metamethods like &lt;code&gt;__call&lt;/code&gt; directly in the metatable and ignores &lt;code&gt;__index&lt;/code&gt;, so I copy them into the new prototype.  Then I make the prototype its own &lt;code&gt;__index&lt;/code&gt;, as with &lt;code&gt;obj&lt;/code&gt;, and also remember the &amp;#8220;superclass&amp;#8221; as &lt;code&gt;__super&lt;/code&gt; (though I never end up using it).  Finally I set the &amp;#8220;superclass&amp;#8221; as the prototype&amp;#8217;s&amp;nbsp;metatable.&lt;/p&gt;
&lt;p&gt;(Oh, by the way: in Lua, if you call a function with only a single table or string literal as its argument, you can leave off the parentheses.  So &lt;code&gt;foo{}&lt;/code&gt; just means &lt;code&gt;foo({})&lt;/code&gt;.)&lt;/p&gt;
&lt;p&gt;That produces something like the following, noting that this is not quite real Lua&amp;nbsp;syntax:&lt;/p&gt;
&lt;table class="highlighttable"&gt;&lt;tr&gt;&lt;td class="linenos"&gt;&lt;div class="linenodiv"&gt;&lt;pre&gt;&lt;span class="normal"&gt;1&lt;/span&gt;
&lt;span class="normal"&gt;2&lt;/span&gt;
&lt;span class="normal"&gt;3&lt;/span&gt;
&lt;span class="normal"&gt;4&lt;/span&gt;
&lt;span class="normal"&gt;5&lt;/span&gt;
&lt;span class="normal"&gt;6&lt;/span&gt;
&lt;span class="normal"&gt;7&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/td&gt;&lt;td class="code"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="kd"&gt;local&lt;/span&gt; &lt;span class="n"&gt;vec&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;__index&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;vec&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;__super&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;obj&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;__call&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;obj&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;__call&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;

    &lt;span class="n"&gt;METATABLE&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;obj&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;p&gt;Remember this&amp;nbsp;syntax?&lt;/p&gt;
&lt;table class="highlighttable"&gt;&lt;tr&gt;&lt;td class="linenos"&gt;&lt;div class="linenodiv"&gt;&lt;pre&gt;&lt;span class="normal"&gt;1&lt;/span&gt;
&lt;span class="normal"&gt;2&lt;/span&gt;
&lt;span class="normal"&gt;3&lt;/span&gt;
&lt;span class="normal"&gt;4&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/td&gt;&lt;td class="code"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="kr"&gt;function&lt;/span&gt; &lt;span class="nc"&gt;vec&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="nf"&gt;init&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;y&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;x&lt;/span&gt; &lt;span class="ow"&gt;or&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;
    &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;y&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;y&lt;/span&gt; &lt;span class="ow"&gt;or&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;
&lt;span class="kr"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;p&gt;That is exactly equivalent&amp;nbsp;to:&lt;/p&gt;
&lt;table class="highlighttable"&gt;&lt;tr&gt;&lt;td class="linenos"&gt;&lt;div class="linenodiv"&gt;&lt;pre&gt;&lt;span class="normal"&gt;1&lt;/span&gt;
&lt;span class="normal"&gt;2&lt;/span&gt;
&lt;span class="normal"&gt;3&lt;/span&gt;
&lt;span class="normal"&gt;4&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/td&gt;&lt;td class="code"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="n"&gt;vec&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;init&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kr"&gt;function&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;y&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;x&lt;/span&gt; &lt;span class="ow"&gt;or&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;
    &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;y&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;y&lt;/span&gt; &lt;span class="ow"&gt;or&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;
&lt;span class="kr"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;p&gt;So after all is said and done, we&amp;nbsp;have:&lt;/p&gt;
&lt;table class="highlighttable"&gt;&lt;tr&gt;&lt;td class="linenos"&gt;&lt;div class="linenodiv"&gt;&lt;pre&gt;&lt;span class="normal"&gt; 1&lt;/span&gt;
&lt;span class="normal"&gt; 2&lt;/span&gt;
&lt;span class="normal"&gt; 3&lt;/span&gt;
&lt;span class="normal"&gt; 4&lt;/span&gt;
&lt;span class="normal"&gt; 5&lt;/span&gt;
&lt;span class="normal"&gt; 6&lt;/span&gt;
&lt;span class="normal"&gt; 7&lt;/span&gt;
&lt;span class="normal"&gt; 8&lt;/span&gt;
&lt;span class="normal"&gt; 9&lt;/span&gt;
&lt;span class="normal"&gt;10&lt;/span&gt;
&lt;span class="normal"&gt;11&lt;/span&gt;
&lt;span class="normal"&gt;12&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/td&gt;&lt;td class="code"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="kd"&gt;local&lt;/span&gt; &lt;span class="n"&gt;vec&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;__index&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;vec&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;__super&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;obj&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;__call&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;obj&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;__call&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;

    &lt;span class="n"&gt;init&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kr"&gt;function&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;...&lt;/span&gt; &lt;span class="kr"&gt;end&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;__add&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kr"&gt;function&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;...&lt;/span&gt; &lt;span class="kr"&gt;end&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;__sub&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kr"&gt;function&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;...&lt;/span&gt; &lt;span class="kr"&gt;end&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;iadd&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kr"&gt;function&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;...&lt;/span&gt; &lt;span class="kr"&gt;end&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;

    &lt;span class="n"&gt;METATABLE&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;obj&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;p&gt;Now for the magic part.  When I call &lt;code&gt;vec()&lt;/code&gt;, Lua checks the metatable.  (The &lt;code&gt;__call&lt;/code&gt; in the main table does nothing!)  The metatable is &lt;code&gt;obj&lt;/code&gt;, which does have a &lt;code&gt;__call&lt;/code&gt;, so Lua calls that function and inserts &lt;code&gt;vec&lt;/code&gt; as the first argument.  Then &lt;code&gt;obj.__call&lt;/code&gt; creates an empty table, assigns &lt;code&gt;self&lt;/code&gt; (which is the first argument, so &lt;code&gt;vec&lt;/code&gt;) as the empty table&amp;#8217;s metatable, and calls the new table&amp;#8217;s &lt;code&gt;init&lt;/code&gt; method.&lt;/p&gt;
&lt;p&gt;Ah, but the new table is empty, so it doesn&amp;#8217;t &lt;em&gt;have&lt;/em&gt; an &lt;code&gt;init&lt;/code&gt; method.  No problem: it has a metatable with an &lt;code&gt;__index&lt;/code&gt;, so Lua consults that instead.  The metatable&amp;#8217;s &lt;code&gt;__index&lt;/code&gt; is &lt;code&gt;vec&lt;/code&gt;, and &lt;code&gt;vec&lt;/code&gt; &lt;em&gt;does&lt;/em&gt; contain an &lt;code&gt;init&lt;/code&gt;, so that&amp;#8217;s what gets called.  (If there were no &lt;code&gt;vec.init&lt;/code&gt;, then Lua would see that vec &lt;em&gt;also&lt;/em&gt; has a metatable with an &lt;code&gt;__index&lt;/code&gt;, and continued along.  That&amp;#8217;s why I didn&amp;#8217;t need an &lt;code&gt;anise.init&lt;/code&gt;.)&lt;/p&gt;
&lt;p&gt;That&amp;#8217;s also why defining &lt;code&gt;vec:__add&lt;/code&gt; works — it puts the &lt;code&gt;__add&lt;/code&gt; metamethod into &lt;code&gt;vec&lt;/code&gt;, which becomes the metatable for all vector objects, thus automatically making &lt;code&gt;+&lt;/code&gt; work on&amp;nbsp;them.&lt;/p&gt;
&lt;p&gt;That&amp;#8217;s all there is to it.  It&amp;#8217;s possible to get much more elaborate with this in a number of ways, but this is the bare minimum — and it could still be trimmed down&amp;nbsp;further.&lt;/p&gt;
&lt;p&gt;Note that you can&amp;#8217;t actually call &lt;code&gt;obj&lt;/code&gt; itself.  Pop quiz: why&amp;nbsp;not?&lt;/p&gt;</content><category term="blog"></category><category term="tech"></category><category term="gamedev"></category></entry><entry><title>Eevee gained 3367 experience points</title><link href="https://eev.ee/blog/2021/01/24/eevee-gained-3367-experience-points/" rel="alternate"></link><published>2021-01-24T11:35:00-08:00</published><updated>2021-01-24T11:35:00-08:00</updated><author><name>Eevee</name></author><id>tag:eev.ee,2021-01-24:/blog/2021/01/24/eevee-gained-3367-experience-points/</id><summary type="html">&lt;p&gt;Eevee grew to level 34!&lt;/p&gt;
&lt;p&gt;I super almost forgot to write one of these!&lt;/p&gt;
&lt;p&gt;What a very, very long year.  I went back through my dev journal to see what I’d done and could not believe most of this happened in the past year.  Even stuff from August feels like it must have been at least a year ago.&lt;/p&gt;
</summary><content type="html">&lt;p&gt;Eevee grew to level&amp;nbsp;34!&lt;/p&gt;
&lt;p&gt;I super almost forgot to write one of&amp;nbsp;these!&lt;/p&gt;
&lt;p&gt;What a very, very long year.  I went back through my dev journal to see what I&amp;#8217;d done and could not believe most of this happened in the past year.  Even stuff from August feels like it must have been at least a year&amp;nbsp;ago.&lt;/p&gt;


&lt;hr /&gt;
&lt;p&gt;I made our first Steam release: &lt;a href="https://store.steampowered.com/app/1259530/Cherry_Kisses/"&gt;Cherry Kisses&lt;/a&gt;, a polished version of a jam game we made (though didn&amp;#8217;t finish in time to get in the jam, oops) the year before.  It&amp;#8217;s sold pretty decently, especially considering the reduced audience (adult games are hidden on Steam unless you opt into them), so that&amp;#8217;s been&amp;nbsp;nice.&lt;/p&gt;
&lt;p&gt;I started &lt;span class="caps"&gt;HRT&lt;/span&gt;!  Then I stopped &lt;span class="caps"&gt;HRT&lt;/span&gt;.&amp;nbsp;Alas.&lt;/p&gt;
&lt;p&gt;I dipped my toes into Godot for real this time with &lt;a href="https://eevee.itch.io/rogue-ike"&gt;Rogue Ike&lt;/a&gt;, a Strawberry Jam game that was perhaps much too ambitious for a first (and time-limited) attempt but worked out as a proof of concept.  I don&amp;#8217;t think I&amp;#8217;ll pick this back up until I&amp;#8217;ve made something a bit more substantial, though; I&amp;#8217;ve got a lot of bits and pieces of Godot code now but still don&amp;#8217;t feel like I have a solid grasp of how I&amp;#8217;d approach architecture for a new&amp;nbsp;game.&lt;/p&gt;
&lt;p&gt;I poured some feelings into a little &lt;span class="caps"&gt;PICO&lt;/span&gt;-8 game: &lt;a href="https://eevee.itch.io/anise-wheres-twig"&gt;Star Anise Chronicles: Oh No Wheres Twig??&lt;/a&gt;, a charming little platformer about my cat&amp;#8217;s fursona.  It&amp;#8217;s probably my favorite thing I&amp;#8217;ve ever&amp;nbsp;released.&lt;/p&gt;
&lt;p&gt;I did actually an &lt;a href="https://eev.ee/dev/2020/08/04/fox-flux-three-years-later/"&gt;incredible lot of work on fox flux&lt;/a&gt; over the summer and made massive strides with it in a relatively short span of time?  It was a broken hopeless mess at the start of the year, and now it&amp;#8217;s&amp;#8230;  well, not.  Better art, better physics, more plot ideas, a lot more little bits and pieces implemented, a whole minigame conceived and mostly implemented, a level tally&amp;#8230;  it feels like a real game,&amp;nbsp;even!&lt;/p&gt;
&lt;p&gt;I also took a crack at a possible port of fox flux to Godot, which was informative both about Godot itself and about designing complex actors in general, but I don&amp;#8217;t think I&amp;#8217;m going to continue with it.  Godot does make some stuff easier, but at the cost of a lot of rough edges that will seriously slow me down — a lot of basic functionality I&amp;#8217;ve been taking for granted in my current setup of LÖVE-duct-taped-to-Tiled just does not exist in Godot, and some of the 2D tooling has major oversights that I&amp;#8217;d have to work around.  Some of this will be fixed in Godot 4, but I don&amp;#8217;t want to wait for that just to continue on this game that I&amp;#8217;ve already poured a lot of work into.  I&amp;#8217;ll probably do something simpler in Godot 4 when it comes&amp;nbsp;out.&lt;/p&gt;
&lt;p&gt;I poured most of the last four months of the year into a surprise project, by which I mean, I surprised myself by doing it: &lt;a href="https://eev.ee/release/2020/09/26/lexys-labyrinth/"&gt;Lexy&amp;#8217;s Labyrinth&lt;/a&gt;, a web-based and unencumbered Chip&amp;#8217;s Challenge 2 emulator — the first of its kind!  It still has some teeny compatibility issues, but for the most part it faithfully plays both the original Chip&amp;#8217;s Challenge 1 and 2 as well as tons of community levels created over the years.  It needs a bit more polish, and then I&amp;#8217;m gonna call it &amp;#8220;basically finished&amp;#8221; and make a bigger effort to drum up interest in&amp;nbsp;it.&lt;/p&gt;
&lt;p&gt;I think I worked on baz, the game engine I wanted to make that was intended as a bridge between MegaZeux and PuzzleScript and bitsy?  But I haven&amp;#8217;t touched it in a while now.  I also started a web-based Sudoku player and then lost interest in Sudoku again.  And then there was the &lt;span class="caps"&gt;AC&lt;/span&gt;:&lt;span class="caps"&gt;NH&lt;/span&gt; companion, which I kept up with until I lost interest in Animal Crossing.&amp;nbsp;Hmmm.&lt;/p&gt;
&lt;p&gt;I did dip my toe back into blogging with the well-received &lt;a href="https://eev.ee/blog/2020/02/01/old-css-new-css/"&gt;&lt;span class="caps"&gt;CSS&lt;/span&gt; post&lt;/a&gt;, and then not so much for a while.  I started the &amp;#8220;&lt;a href="https://eev.ee/blog/2020/11/30/gamedev-from-scratch-0-groundwork/"&gt;gamedev from scratch&lt;/a&gt;&amp;#8221; series to replace the ill-fated book I toyed with writing, but it has yet to see a second&amp;nbsp;installment.&lt;/p&gt;
&lt;hr /&gt;
&lt;p&gt;I feel like I miss making video games, even though I did rather a lot of it this year.  I guess I don&amp;#8217;t feel like I &lt;em&gt;released&lt;/em&gt; any; Cherry Kisses was an existing game, Rogue Ike didn&amp;#8217;t get further than a handful of rooms, fox flux is still quite a ways off from being done, and Lexy&amp;#8217;s Labyrinth is really a game &lt;em&gt;engine&lt;/em&gt;.  That leaves the Star Anise game as the only “““real””” one I released, but that may not be an entirely fair way to gauge how much work I&amp;#8217;ve&amp;nbsp;done.&lt;/p&gt;
&lt;p&gt;I do miss writing more often.  I guess after everything that happened three years ago, I never quite figured out how to reconnect with the universe.  Sometimes I go on a tweeting spree for a couple days, and that feels nostalgic, but in general I&amp;#8217;ve gotten more withdrawn and don&amp;#8217;t quite know how to shake it.  I&amp;#8217;m still&amp;nbsp;trying.&lt;/p&gt;
&lt;p&gt;I like how well Cherry Kisses did, and I&amp;#8217;d kinda like to do small adult game releases more regularly — they&amp;#8217;re fun to design and write, they make folks happy, and they bring in a steady trickle of sales.  I have a concept for one I&amp;#8217;m going to start on during &lt;a href="https://itch.io/jam/strawberry-jam-5"&gt;Strawberry Jam 5&lt;/a&gt; next month, but it&amp;#8217;s a bit more ambitious, so I might have to do something smaller for Steam purposes this year.  Maybe I should take this as an opportunity to get a real foothold in Godot?  Cherry Kisses wasn&amp;#8217;t terribly complicated; I could recreate something like that without much trouble, and spend some time ironing out&amp;nbsp;wrinkles.&lt;/p&gt;
&lt;p&gt;I do want to keep working on fox flux — it&amp;#8217;s been just about four years since the jam version now, and I still love the idea and would like to get it seriously going.  I&amp;#8217;ve spent so much time on engine and design stuff that I still barely have any areas to&amp;nbsp;show!&lt;/p&gt;
&lt;p&gt;And of course I would very much love to get that gamedev from scratch series going.  I promised one installment per month, and I already missed December because I was neck-deep in Lexy&amp;#8217;s Labyrinth, so I really ought to write &lt;em&gt;two&lt;/em&gt; in the next week.  We&amp;#8217;ll see how that&amp;nbsp;goes.&lt;/p&gt;
&lt;hr /&gt;
&lt;p&gt;I don&amp;#8217;t know how I feel about being 34.  Solidly in my mid-30s.  I still remember the days, twenty years ago now, when I was the youngest person I knew in almost any circles: online, at school, whatever.  Now I&amp;#8217;m usually one of the oldest, as most folks my age are off with children and careers; whereas I&amp;#8217;ve made a life out of making weird stuff on the internet, just like I did as a&amp;nbsp;teenager.&lt;/p&gt;
&lt;p&gt;Still, I guess that means I&amp;#8217;m exactly where I always wanted to&amp;nbsp;be.&lt;/p&gt;
&lt;hr /&gt;
&lt;p&gt;Browsers all have autoplay restrictions now, so you&amp;#8217;ll have to hit play on this&amp;nbsp;yourself.&lt;/p&gt;
&lt;p&gt;&lt;audio src="/media/2012-01/levelup.ogv" controls autoplay&gt;&lt;/p&gt;</content><category term="blog"></category><category term="birthday"></category><category term="personal"></category></entry><entry><title>Gamedev from scratch 0: Groundwork</title><link href="https://eev.ee/blog/2020/11/30/gamedev-from-scratch-0-groundwork/" rel="alternate"></link><published>2020-11-30T14:58:00-08:00</published><updated>2020-11-30T14:58:00-08:00</updated><author><name>Eevee</name></author><id>tag:eev.ee,2020-11-30:/blog/2020/11/30/gamedev-from-scratch-0-groundwork/</id><summary type="html">&lt;p&gt;You may recall that I once had the ambitious idea to write a book on game development, walking the reader through making simple games &lt;em&gt;from scratch&lt;/em&gt; in a variety of different environments, starting from simple level editors and culminating in some “real” engine.&lt;/p&gt;
&lt;p&gt;That never quite materialized.  As it turns out, writing a book is a huge slog, publishers want almost all of the proceeds, and LaTeX is an endless rabbit hole of distractions that probably consumed more time than actually writing.  Also, a book about programming with no copy/paste or animations or hyperlinks kind of sucks.&lt;/p&gt;
&lt;p&gt;I thus present to you Plan B: a series of blog posts.  This is a narrative reconstruction of a small game I made recently, &lt;a href="https://eev.ee/release/2020/05/10/star-anise-chronicles-oh-no-wheres-twig/"&gt;Star Anise Chronicles: Oh No Wheres Twig??&lt;/a&gt;.  It took me less than two weeks and I kept quite a few snapshots of the game’s progress, so you’ll get to see a somewhat realistic jaunt through the process of creating a small game from very nearly nothing.&lt;/p&gt;
&lt;p&gt;And unlike your typical programming tutorial, I can &lt;em&gt;guarantee&lt;/em&gt; that this won’t get you as far as a half-assed Mario clone and then abruptly end.  The game has original art and sound, a title screen, an ending, cutscenes, dialogue, &lt;span class="caps"&gt;UI&lt;/span&gt;, and more — so this series will necessarily cover how all of that came about.  I will tell you why I made particular decisions, mention planned features I cut, show you the tradeoffs I made, and confess when I made life harder for myself.  You know, all the stuff you &lt;em&gt;actually go through&lt;/em&gt; when doing game development (or, frankly, any kind of software development).&lt;/p&gt;
&lt;p&gt;The target audience is (ideally) anyone who knows what a computer is, so hopefully you can follow along no matter what your experience level. Enjoy!&lt;/p&gt;
&lt;hr/&gt;
&lt;p&gt;This is &lt;strong&gt;part zero&lt;/strong&gt;, and it’s mostly introductory stuff.  Please don’t skip it!  I promise there’s some meat in the latter half.&lt;/p&gt;
</summary><content type="html">&lt;p&gt;You may recall that I once had the ambitious idea to write a book on game development, walking the reader through making simple games &lt;em&gt;from scratch&lt;/em&gt; in a variety of different environments, starting from simple level editors and culminating in some &amp;#8220;real&amp;#8221;&amp;nbsp;engine.&lt;/p&gt;
&lt;p&gt;That never quite materialized.  As it turns out, writing a book is a huge slog, publishers want almost all of the proceeds, and LaTeX is an endless rabbit hole of distractions that probably consumed more time than actually writing.  Also, a book about programming with no copy/paste or animations or hyperlinks kind of&amp;nbsp;sucks.&lt;/p&gt;
&lt;p&gt;I thus present to you Plan B: a series of blog posts.  This is a narrative reconstruction of a small game I made recently, &lt;a href="https://eev.ee/release/2020/05/10/star-anise-chronicles-oh-no-wheres-twig/"&gt;Star Anise Chronicles: Oh No Wheres Twig??&lt;/a&gt;.  It took me less than two weeks and I kept quite a few snapshots of the game&amp;#8217;s progress, so you&amp;#8217;ll get to see a somewhat realistic jaunt through the process of creating a small game from very nearly&amp;nbsp;nothing.&lt;/p&gt;
&lt;p&gt;And unlike your typical programming tutorial, I can &lt;em&gt;guarantee&lt;/em&gt; that this won&amp;#8217;t get you as far as a half-assed Mario clone and then abruptly end.  The game has original art and sound, a title screen, an ending, cutscenes, dialogue, &lt;span class="caps"&gt;UI&lt;/span&gt;, and more — so this series will necessarily cover how all of that came about.  I will tell you why I made particular decisions, mention planned features I cut, show you the tradeoffs I made, and confess when I made life harder for myself.  You know, all the stuff you &lt;em&gt;actually go through&lt;/em&gt; when doing game development (or, frankly, any kind of software&amp;nbsp;development).&lt;/p&gt;
&lt;p&gt;The target audience is (ideally) anyone who knows what a computer is, so hopefully you can follow along no matter what your experience level.&amp;nbsp;Enjoy!&lt;/p&gt;
&lt;hr /&gt;
&lt;p&gt;This is &lt;strong&gt;part zero&lt;/strong&gt;, and it&amp;#8217;s mostly introductory stuff.  Please don&amp;#8217;t skip it!  I promise there&amp;#8217;s some meat in the latter&amp;nbsp;half.&lt;/p&gt;


&lt;h2 id="table-of-contents"&gt;&lt;a class="toclink" href="#table-of-contents"&gt;Table of&amp;nbsp;contents&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Here&amp;#8217;s what you have to look forward to (though it is of course a &lt;span class="caps"&gt;WIP&lt;/span&gt; until the series is done).  Occasionally there&amp;#8217;ll be a &lt;em&gt;snapshot&lt;/em&gt; of the game, but these were made on a whim during development and aren&amp;#8217;t particularly meaningful as&amp;nbsp;milestones.&lt;/p&gt;
&lt;p&gt;For reference, I started working on the game the morning of April 29, and I released it the night of May 10, for a total of twelve&amp;nbsp;days.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Part 0: Groundwork&lt;/strong&gt; (you are here) — introduction, tour of &lt;span class="caps"&gt;PICO&lt;/span&gt;-8, putting something on the screen, moving around, measuring time, simple sprite&amp;nbsp;animation&lt;/li&gt;
&lt;li&gt;&lt;a href="https://eev.ee/blog/2021/01/26/gamedev-from-scratch-1-scaffolding/"&gt;Part 1: Scaffolding&lt;/a&gt; — structure, objects in Lua, a taste of collision&amp;nbsp;detection&lt;/li&gt;
&lt;/ul&gt;
&lt;!--
* Part 1: objects, basic architecture, collision [snapshot v1 — Apr 29, 7:31pm]

* custom palette
* collision detection
* giving anise abilities
* a HUD for the abilities
* doors
* glasses
* NPCs
* a title screen and scene switching
* a background
* telepawt
* landing particles
* inventory switch animation
* sprite anchors
* smoke vent
* text shadows
* screen transitions and a parallax background
* scrolling dialogue
* stars and particle effects
* remaining NPCs
* intro cutscene
* items and animations for using them
* sparkles
* most of the first half of the plot
* converting to coroutines
* first pass at code size
* flags for map connections
* locked "doors"
* credits
* waterfall
* 
--&gt;

&lt;!--
Apr 29 19:31 anise1.5-v1.p8     sort, vec, mobactor (including collision), anise moves around
Apr 30 22:53 anise1.5-v2.p8     fix partial movement (?), anise abilities, doors, ui for them, pickup i think
May  3 22:55 anise1.5-v3.p8     glasses, buttons, npcs, scenes, basic title screen, background, telepawt, shutter util type, stars?
May  4 02:04 anise1.5-v4.p8     inventory animation, anchors back, smoke vent
May  5 08:12 anise1.5-v5.p8     text shadow, screen transitions and parallax background, scrolling dialogue, stars, remaining lunekos, intro cutscene
May  7 00:49 anise1.5-v6.p8     items and animations for them, sparkles, i think the entire first half, 
May  7 10:44 anise1.5-v7.p8     code size, coros instead of shutter, map connections as flags, keys and locks, waterfall, 
May  7 14:57 anise1.5-v8.p8     more code size mostly
May  8 05:37 anise1.5-v8a.p8    coro dialogue, ?
May  9 04:55 anise1.5-v9.p8     
May 10 11:53 anise1.5-v10.p8
May 10 12:37 anise1.5-v11.p8
--&gt;

&lt;h2 id="introduction"&gt;&lt;a class="toclink" href="#introduction"&gt;Introduction&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;This is not a tutorial.&lt;/strong&gt;  Please set your expectations accordingly.  Honestly, I don&amp;#8217;t even like tutorials — too many of them are framed as something that will teach you a skill, but then only tell you what buttons to press to recreate what the author already made, with no insight as to why they made their decisions or even why they pressed those particular buttons.  They often leave you hanging, with no clear next steps, no explanation of what to adjust to get different&amp;nbsp;results.&lt;/p&gt;
&lt;p&gt;I&amp;#8217;ve never seen a platformer tutorial that actually produced a finished game.  Most of them give you just enough to have a stock sprite (poorly) jump around on the screen, perhaps collect some coins, and that&amp;#8217;s it.  How do you fix the controls, add cutscenes, even make a damn title screen?  That&amp;#8217;s all left up to&amp;nbsp;you.&lt;/p&gt;
&lt;p&gt;This is something much better than a tutorial: a &lt;em&gt;story&lt;/em&gt;.  I made a video game — a real, complete video game — and I will tell you everything I can remember doing &lt;em&gt;and thinking&lt;/em&gt; along the way.  Every careful decision, every rushed tradeoff, every boneheaded mistake, every weird diversion.  I don&amp;#8217;t guarantee that anything I did is necessarily a &lt;em&gt;good&lt;/em&gt; idea, but everything I did is &lt;em&gt;an&lt;/em&gt; idea, and sometimes that&amp;#8217;s all you need to get the gears&amp;nbsp;turning.&lt;/p&gt;
&lt;p&gt;If you&amp;#8217;re interested in making a video games, I don&amp;#8217;t promise that this series will &lt;em&gt;teach&lt;/em&gt; you anything.  But with a little effort, you can probably &lt;em&gt;learn&lt;/em&gt; something.  And to be frank, if you&amp;#8217;re starting with zero knowledge but still manage to muddle through the whole series, you&amp;#8217;ve got more than enough curiosity and determination to succeed at whatever you feel like&amp;nbsp;doing.&lt;/p&gt;
&lt;p&gt;The game in question is &lt;a href="content/release/2020-05-10-star-anise-chronicles-oh-no-wheres-twig.markdown"&gt;Star Anise Chronicles: Oh No Wheres Twig??&lt;/a&gt;, which I made with the &lt;a href="https://www.lexaloffle.com/pico-8.php"&gt;&lt;span class="caps"&gt;PICO&lt;/span&gt;-8&lt;/a&gt;.  (If you are from the future, I specifically used version 0.2.0i; later versions may have added conveniences I&amp;#8217;m not using.)  This is not a whizbang fully-featured game engine like &lt;a href="https://godotengine.org/"&gt;Godot&lt;/a&gt; or &lt;a href="https://unity.com/"&gt;Unity&lt;/a&gt;.  If I want to draw something, I have to draw it myself.  If I want physics, I have to write them myself.  If I want shaders&amp;#8230;  well, that&amp;#8217;s not going to happen, but a little ingenuity can still go a long&amp;nbsp;way.&lt;/p&gt;
&lt;p&gt;And that kind of ingenuity is what makes game development appealing to me in the first place.  It&amp;#8217;s one big puzzle: given the tools I have, what&amp;#8217;s the most interesting thing I can make with the least amount of hapless flailing?  That question will come up a number of times in this&amp;nbsp;series.&lt;/p&gt;
&lt;p&gt;If any of this sounds appealing to you, keep reading!  Follow along if you can.  You can get the &lt;span class="caps"&gt;PICO&lt;/span&gt;-8 (tragically not open source) for $15, and chances are you already own it — it was in the itch.io &lt;a href="https://itch.io/b/520/bundle-for-racial-justice-and-equality"&gt;&lt;span class="caps"&gt;BLM&lt;/span&gt; bundle&lt;/a&gt;, so if you bought that, you&amp;#8217;re free to download it whenever you&amp;nbsp;want.&lt;/p&gt;
&lt;h3 id="conventions"&gt;&lt;a class="toclink" href="#conventions"&gt;Conventions&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;In order to replicate the experience of reading the book, I&amp;#8217;m porting these little &amp;#8220;admonition&amp;#8221; boxes from what I&amp;#8217;d started.  I have a somewhat meandering writing style, and hopefully these will help get tangents out of the main text, while also better highlighting warnings and&amp;nbsp;gotchas.&lt;/p&gt;
&lt;p&gt;Here they are, in no particular&amp;nbsp;order:&lt;/p&gt;
&lt;aside class="aside--well-actually"&gt;
&lt;p&gt;This indicates that I have just told you a little white lie for the sake of simplicity, like a math teacher fibbing that you can't take the square root of negative numbers, but my conscience will not let it go uncommented&amp;nbsp;upon.&lt;/p&gt;
&lt;p&gt;(Actually, Twitter stopped using eggs as default avatars in April&amp;nbsp;2017.)&lt;/p&gt;
&lt;/aside&gt;
&lt;aside class="aside--note-from-future"&gt;
&lt;p&gt;While I did just accurately describe my thought process &lt;em&gt;at the time&lt;/em&gt;, I have arrived from the future to let you know I actually made a very poor decision.  Tragically, my past self cannot see this warning, and we must now together watch her stumble into her impending&amp;nbsp;doom.&lt;/p&gt;
&lt;/aside&gt;
&lt;aside class="aside--computers-are-bad"&gt;
&lt;p&gt;Computers are wonderful tools, but sometimes they are dead set on sabotaging you and everything you hold dear for no good reason.  Beware!  There's some asinine problem here that has no good reason to&amp;nbsp;exist.&lt;/p&gt;
&lt;/aside&gt;
&lt;aside class="aside--fascinating-tangent"&gt;
&lt;p&gt;This has nothing to do with whatever I was just talking about and you can almost certainly ignore it without missing anything important, but I couldn't resist telling you about&amp;nbsp;it.&lt;/p&gt;
&lt;/aside&gt;
&lt;aside class="aside--matter-of-taste"&gt;
&lt;p&gt;Something I just stated as though it were fact is really a matter of opinion (and, most likely, heated debate), but this is my blog, so I can say my opinion is right and delete all the comments that try to argue with&amp;nbsp;me.&lt;/p&gt;
&lt;/aside&gt;
&lt;aside class="aside--look-out"&gt;
&lt;p&gt;Programming is full of pitfalls, and game development is practically overflowing with them.  Like that one you're falling into right&amp;nbsp;now.&lt;/p&gt;
&lt;/aside&gt;
&lt;aside class="aside--tricky-tradeoff"&gt;
&lt;p&gt;You can't always get what you want.  But if you try sometimes, you might find, you can get half of each thing you wanted and it'll be good&amp;nbsp;enough.&lt;/p&gt;
&lt;/aside&gt;
&lt;aside class="aside--deceptively-difficult"&gt;
&lt;p&gt;You'd think this should take like two minutes, right?  Great news: it's gonna consume the next ten hours.  Some things are much harder than they look; don't lose heart if you&amp;nbsp;struggle!&lt;/p&gt;
&lt;/aside&gt;
&lt;p&gt;I reserve the right to invent more, if they&amp;#8217;re needed and/or&amp;nbsp;funny.&lt;/p&gt;
&lt;!--
warning: this is hard or distracting or whatever
don't worry about it: this is a hard part that isn't really important, or i made it more complicated than necessary
- other suggestions for myself: gotcha, useful context, entertaining mistakes, performance, tip/hint/handy trick/nice shortcut (power user?), "good enough for now", doing it right™, quick and dirty...  easy pitfall, 
- go look it up yourself
--&gt;

&lt;h3 id="setting-expectations-again"&gt;&lt;a class="toclink" href="#setting-expectations-again"&gt;Setting expectations,&amp;nbsp;again&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Game development is about a lot more than programming, but this &lt;em&gt;will&lt;/em&gt; contain an awful lot of programming.  The &lt;span class="caps"&gt;PICO&lt;/span&gt;-8 in particular tends to blur the lines between code and assets if you want to do anything&amp;nbsp;fancy.&lt;/p&gt;
&lt;p&gt;That puts me in a tricky position as an author.  I want this to be accessible to people with little or no programming experience, but I can&amp;#8217;t realistically explain every single line of code I write, or this series will never end (and will be more noise than signal for intermediate&amp;nbsp;programmers).&lt;/p&gt;
&lt;p&gt;Thus, I&amp;#8217;m &lt;strong&gt;trusting you&lt;/strong&gt; to look up basic concepts on your own if you need to.  I&amp;#8217;m writing this to fill a perceived gap, so I&amp;#8217;ll try to focus on the gaps — finding resources on from-scratch collision detection is a crapshoot, but the web is awash in explanations of what a &amp;#8220;variable&amp;#8221; is.  &lt;span class="caps"&gt;PICO&lt;/span&gt;-8 uses a programming language called &lt;a href="https://www.lua.org/"&gt;Lua&lt;/a&gt; which is pretty simple and easy to pick up, so if you&amp;#8217;re having trouble, maybe thumb through the &lt;a href="https://www.lua.org/pil/contents.html"&gt;Programming in Lua&lt;/a&gt; book a bit&amp;nbsp;too.&lt;/p&gt;
&lt;p&gt;Of course, if you&amp;#8217;re just here for the ride and not too worried about writing your own game, you can skip ahead whenever you like.  I&amp;#8217;m not your&amp;nbsp;mom.&lt;/p&gt;
&lt;p&gt;(Oh, and if you&amp;#8217;ve used Lua before, you should know that &lt;span class="caps"&gt;PICO&lt;/span&gt;-8&amp;#8217;s Lua has been modified from stock Lua.  The precise list of changes would be a big block of stuff in the middle of this already too long intro, so I&amp;#8217;ve put it &lt;a href="#appendix-pico-8-lua-extensions"&gt;at the bottom&lt;/a&gt;.  The upshot is: numbers are fixed-point instead of floating-point, you can use compound assignment, and the standard library is almost completely&amp;nbsp;different.)&lt;/p&gt;
&lt;p&gt;That&amp;#8217;s probably enough words with no pictures.  Time to get&amp;nbsp;started.&lt;/p&gt;
&lt;h2 id="the-pico-8"&gt;&lt;a class="toclink" href="#the-pico-8"&gt;The&amp;nbsp;PICO-8&lt;/a&gt;&lt;/h2&gt;
&lt;div class="prose-full-illustration"&gt;
&lt;img src="https://eev.ee/media/gamedev-from-scratch/pico8-startup.png" alt="A fresh PICO-8 window, with white old-school text on a small black screen and a command prompt"&gt;
&lt;/div&gt;

&lt;p&gt;As mentioned, this is a game built with the &lt;span class="caps"&gt;PICO&lt;/span&gt;-8.  I promised I&amp;#8217;d tell you a story, but I can&amp;#8217;t even explain why I chose &lt;span class="caps"&gt;PICO&lt;/span&gt;-8 if you don&amp;#8217;t know what the thing &lt;em&gt;is&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;&lt;span class="caps"&gt;PICO&lt;/span&gt;-8 is a &amp;#8220;fantasy console&amp;#8221; — a genre that it pioneered.  It has a fixed screen size, its own palette, its own font, a little chiptune synthesizer, its own idea of what buttons the player can press, and so on.  It&amp;#8217;s like an emulator for an 8-bit handheld that doesn&amp;#8217;t actually exist, plus a bunch of relatively friendly tools for making cartridges for that handheld.  It even has some arbitrary limitations to preserve that aesthetic.  (I carefully avoid calling them &lt;em&gt;artificial&lt;/em&gt; limitations, because there &lt;em&gt;are&lt;/em&gt; some technical reasons for them, and a lot of programmers do a thing with their face if you say &amp;#8220;artificial&amp;#8221; to them.  Like you&amp;#8217;ve just spat in their&amp;nbsp;lunch.)&lt;/p&gt;
&lt;p&gt;If you&amp;#8217;ve got &lt;span class="caps"&gt;PICO&lt;/span&gt;-8 open, you can type &lt;code&gt;splore&lt;/code&gt; at this little command prompt to open the cartridge explorer, which lets you download and play cartridges that have been posted to the &lt;span class="caps"&gt;PICO&lt;/span&gt;-8 &lt;span class="caps"&gt;BBS&lt;/span&gt; (forum).  You might want to try a few to get a sense of what the &lt;span class="caps"&gt;PICO&lt;/span&gt;-8 can do, though bear in mind that some of the best games are incredible feats of ingenuity and not representative.  A good place to start is the &amp;#8220;featured&amp;#8221; tab, which lists games that&amp;#8230;  I believe have been hand-picked as high-quality?  Some&amp;nbsp;suggestions:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Star Anise Chronicles: Oh No Wheres Twig is in there, as is our older (and first!) game &lt;a href="https://eevee.itch.io/under-construction"&gt;Under Construction&lt;/a&gt;.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;The original &lt;span class="caps"&gt;PICO&lt;/span&gt;-8 version of Celeste, if you weren&amp;#8217;t aware of its&amp;nbsp;origins.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Dusk Child, one of the earliest games I played and a big inspiration — it&amp;#8217;s pretty and expansive, but doesn&amp;#8217;t do anything I couldn&amp;#8217;t figure&amp;nbsp;out.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Just One Boss, which is just so damn &lt;em&gt;crisp&lt;/em&gt;.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Dank Tomb, a dungeon crawler with absolutely beautiful lighting&amp;nbsp;effects.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;PicoHot, which is absolute fucking nonsense how dare&amp;nbsp;you.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Note that when playing most games, the &lt;span class="caps"&gt;PICO&lt;/span&gt;-8 functions as though it only had six buttons: a directional pad bound to the arrow keys, and &amp;#8220;O&amp;#8221; and &amp;#8220;X&amp;#8221; buttons bound to the &lt;kbd&gt;Z&lt;/kbd&gt; and &lt;kbd&gt;X&lt;/kbd&gt; keys.  Most games refer to those buttons by name (the &lt;span class="caps"&gt;PICO&lt;/span&gt;-8 font has built-in symbols for them) rather than keyboard key, since you might be playing on a controller or with some other bindings.  You can always press &lt;kbd&gt;Esc&lt;/kbd&gt; for the built-in&amp;nbsp;menu.&lt;/p&gt;
&lt;p&gt;&amp;#8230;&lt;/p&gt;
&lt;p&gt;Had fun?  Great!  Pressing &lt;kbd&gt;Esc&lt;/kbd&gt; takes you back to the prompt.  From there, you can press &lt;kbd&gt;Esc&lt;/kbd&gt; again to switch to the editor (and vice&amp;nbsp;versa).&lt;/p&gt;
&lt;p&gt;Now, this is not a &lt;span class="caps"&gt;PICO&lt;/span&gt;-8 tutorial.  But the &lt;span class="caps"&gt;PICO&lt;/span&gt;-8&amp;#8217;s design and constraints &lt;em&gt;immensely&lt;/em&gt; impact how much I could do and how I planned to do it, so I can&amp;#8217;t very well explain my thought process without that context.  Luckily, all the code and assets for the last game you played stay loaded, so I might as well give you the whirlwind tour.  Even if you&amp;#8217;re not following along with an actual copy of &lt;span class="caps"&gt;PICO&lt;/span&gt;-8, you should keep reading so you understand what I&amp;#8217;ve got to work&amp;nbsp;with.&lt;/p&gt;
&lt;h3 id="code-editor"&gt;&lt;a class="toclink" href="#code-editor"&gt;Code&amp;nbsp;editor&lt;/a&gt;&lt;/h3&gt;
&lt;div class="prose-full-illustration"&gt;
&lt;img src="https://eev.ee/media/gamedev-from-scratch/pico8-editor-code.png" alt="A very small text editor, populated with code"&gt;
&lt;/div&gt;

&lt;p&gt;This is the code editor, a very tiny text editor.  If you&amp;#8217;ve loaded Under Construction, feel free to page through and see what I did.  (Keyboard shortcuts help a lot; see &lt;a href="https://www.lexaloffle.com/pico-8.php?page=manual"&gt;the manual&lt;/a&gt; for a full list of them.  There are also some &lt;a href="https://wh0am1.dev/pico8-api/"&gt;cheat sheets&lt;/a&gt; floating around, though they focus more on programming&amp;nbsp;capabilities.)&lt;/p&gt;
&lt;p&gt;You may have noticed the ominous &lt;code&gt;7695/8192&lt;/code&gt; in the bottom right.  That&amp;#8217;s hinting at one of the &lt;span class="caps"&gt;PICO&lt;/span&gt;-8&amp;#8217;s limitations: the &lt;em&gt;token count&lt;/em&gt;.  A cartridge&amp;#8217;s source code cannot exceed 8192 tokens, or it will not run at all.  A &amp;#8220;token&amp;#8221; is, in general terms, a single &amp;#8220;word&amp;#8221; of code — a number like &lt;code&gt;133&lt;/code&gt;, a name like &lt;code&gt;animframedelay&lt;/code&gt;, an operator like &lt;code&gt;+&lt;/code&gt;, a keyword like &lt;code&gt;function&lt;/code&gt;, and so on.  The term &amp;#8220;token&amp;#8221; is borrowed from the field of parsing, which is an entire tangent you are free to look up&amp;nbsp;yourself.&lt;/p&gt;
&lt;p&gt;The &lt;span class="caps"&gt;PICO&lt;/span&gt;-8&amp;#8217;s definition of &amp;#8220;token&amp;#8221; is slightly different from its typical usage and includes a few exceptions.  The common Lua keywords &lt;code&gt;local&lt;/code&gt; and &lt;code&gt;end&lt;/code&gt; don&amp;#8217;t count at all; nor do commas, periods, semicolons, or comments.  A string of any length is one token.  A &lt;em&gt;pair&lt;/em&gt; of parentheses, brackets, or braces only counts as one token.  Negative literal numbers (e.g., &lt;code&gt;-25&lt;/code&gt;) are one&amp;nbsp;token.&lt;/p&gt;
&lt;aside class="aside--fascinating-tangent"&gt;
&lt;p&gt;I suspect the reason for this is to emulate, very roughly, what it was like to write code for 8-bit hardware.  If you've read any of my posts on my brief (and aborted) dive into Game Boy development, you've seen it was all written in assembly, where everything is built out of very simple terse instruction like &lt;code&gt;add a, 8&lt;/code&gt;.  More math means more instructions, and each instruction takes up a byte or two.  In PICO-8, more math means more &lt;em&gt;tokens&lt;/em&gt;, so it works out similarly.  Jumping to a label in assembly only needs space for the jump, not the label, so the Lua equivalent of &lt;code&gt;end&lt;/code&gt; doesn't take a token.  Nesting is meaningless in assembly, so parentheses only count as one (eh).  Reading from fields is an address addition plus a read, so the &lt;code&gt;.&lt;/code&gt; in the middle shouldn't count.  And so&amp;nbsp;on.&lt;/p&gt;
&lt;p&gt;Ironically, there is &lt;em&gt;vastly&lt;/em&gt; more space on a Game Boy than in the PICO-8 — the original Pokémon games had a whopping half a meg to work with, eight times more than the PICO-8's&amp;nbsp;64KiB.&lt;/p&gt;
&lt;/aside&gt;
&lt;p&gt;The token limit is the most oppressive of the limits on your code, but there are two others.  The full size of your code cannot exceed 64KiB, though in practice I&amp;#8217;ve never come anywhere near that size and I think you&amp;#8217;d only approach it if you were committing some serious shenanigans.  More of concern, the &lt;em&gt;compressed&lt;/em&gt; size of your code cannot exceed 15,616 bytes.  I do wind up battling that one near the end of this project (as I did with Under Construction), and it can be extra frustrating since it&amp;#8217;s hard to gauge exactly what impact any particular change will have on compression.  Thankfully, and unlike with the token limit, the &lt;span class="caps"&gt;PICO&lt;/span&gt;-8 will still run a game that&amp;#8217;s over the compressed size; it just physically cannot export it to a&amp;nbsp;cartridge.&lt;/p&gt;
&lt;p&gt;Incidentally, you can use &lt;kbd&gt;Alt&lt;/kbd&gt; and an arrow key to move between the&amp;nbsp;editors.&lt;/p&gt;
&lt;h3 id="sprite-editor"&gt;&lt;a class="toclink" href="#sprite-editor"&gt;Sprite&amp;nbsp;editor&lt;/a&gt;&lt;/h3&gt;
&lt;div class="prose-full-illustration"&gt;
&lt;img src="https://eev.ee/media/gamedev-from-scratch/pico8-editor-sprite.png" alt="A very small sprite editor, showing the mole player character from Under Construction"&gt;
&lt;/div&gt;

&lt;p&gt;Here we have a tiny pixel art editor.  As you might have guessed, the &amp;#8220;native&amp;#8221; size for a tile is 8 × 8 pixels, though you can use the bottom of the two sliders to edit bigger blocks of tiles at a time.  (The screen is 128 × 128 pixels, or 16 × 16 tiles.)  You have at your disposal a spritesheet of 256 such tiles, which are arranged at the bottom of the screen in four tabs of 64 tiles each.  &lt;code&gt;001&lt;/code&gt; here is the tile number.  Each tile has its own set of 8 flags you can toggle on and off, which are represented by the eight circles just above the tabs; here, all the flags are off.  The flags do nothing by themselves, but you can use them for whatever you like, and they turn out to be pretty&amp;nbsp;handy.&lt;/p&gt;
&lt;p&gt;The palette is 16 colors, as shown.  There are 16 more colors on the &amp;#8220;secret palette&amp;#8221; which I&amp;#8217;ll be dipping into later, but you can only swap them in; you can never have more than 16 distinct colors on screen at the same time.  This is reminiscent of how some early systems actually&amp;nbsp;worked.&lt;/p&gt;
&lt;h3 id="map-editor"&gt;&lt;a class="toclink" href="#map-editor"&gt;Map&amp;nbsp;editor&lt;/a&gt;&lt;/h3&gt;
&lt;div class="prose-full-illustration"&gt;
&lt;img src="https://eev.ee/media/gamedev-from-scratch/pico8-editor-map.png" alt="A very small map editor, showing the upper left of a cave-like area from Under Construction"&gt;
&lt;/div&gt;

&lt;p&gt;The map editor edits the map.  You only get one; if you want to carve it up somehow, that&amp;#8217;s up to you.  It&amp;#8217;s extremely simple: you have a grid of 128 × 64 tiles (that&amp;#8217;s 8 × 4 screenfuls), and you can pick which tile goes in each cell.  No layers, no stacking, no two things in the same cell.  You can pan around with the middle mouse button and zoom with the mouse wheel (or check the manual for the keyboard&amp;nbsp;equivalents).&lt;/p&gt;
&lt;p&gt;The especially nice thing about the map is that you can draw entire blocks of it with the built-in &lt;code&gt;map&lt;/code&gt; function, which saves a whole lot of tokens over drawing a bunch of tiles by hand.  Even if you&amp;#8217;re making a game that doesn&amp;#8217;t have a literal map, it&amp;#8217;s a convenient way to define and draw blocks of multiple&amp;nbsp;tiles.&lt;/p&gt;
&lt;aside class="aside--fascinating-tangent"&gt;
&lt;p&gt;Actual hardware like the Game Boy required you to put "most" of your tiles in a grid, with a limited number drawn on top wherever you want.  The PICO-8 doesn't have this restriction — you can draw whatever you want at any position — but the convenience of drawing tiles en masse from the map still acts as gentle guidance towards that&amp;nbsp;aesthetic.&lt;/p&gt;
&lt;/aside&gt;
&lt;p&gt;The catch is that the bottom half of the spritesheet and the bottom half of the map are &lt;em&gt;shared&lt;/em&gt;, so you can&amp;#8217;t actually have a full map and a full set of tiles in the same cartridge.  You could have a full 8 × 4 map and 128 tiles, or you could have a full set of 256 tiles but only an 8 × 2 map, or you can split the space up somehow, but you can&amp;#8217;t have the maximum of both.  Drawing in the bottom half of one will immediately update the other with garbage.  It&amp;#8217;s beautiful, actually, if you&amp;#8217;re into the aesthetic of arbitrary memory being drawn as&amp;nbsp;tiles.&lt;/p&gt;
&lt;p&gt;If you have a cartridge open, you can see this yourself: check out the bottom half of the map (it helps to use &lt;kbd&gt;Tab&lt;/kbd&gt; or the buttons in the upper left to hide the tile palette) and tabs 2 and 3 of the sprite editor.  If they&amp;#8217;re not both completely empty, &lt;em&gt;something&lt;/em&gt; will be full of garbage.  Try drawing in one or the other, if you like, and you&amp;#8217;ll see the other update with junk.  That&amp;#8217;s the memory layout of pixel data being interpreted as map data, or vice versa.  Cool,&amp;nbsp;right?&lt;/p&gt;
&lt;aside class="aside--fascinating-tangent"&gt;
&lt;p&gt;If you hadn't guessed by now, the PICO-8 has its own faux ROM/RAM layout, which you can even inspect and modify directly, so all these numbers aren't chosen just for kicks.  You can't have more than 256 tiles because the map uses one byte per tile; you can't have both a full map and full spritesheet because the shared section literally occupies the same area of ROM, and there's no room for expansion because the 16-bit address space is full!  This is the same reason the compressed code limit is the oddball size of 15,616 bytes — that's how much space was left over once everything else was accounted&amp;nbsp;for.&lt;/p&gt;
&lt;p&gt;Boy, I sure am using these things a lot&amp;nbsp;already.&lt;/p&gt;
&lt;/aside&gt;
&lt;h3 id="sound-editor"&gt;&lt;a class="toclink" href="#sound-editor"&gt;Sound&amp;nbsp;editor&lt;/a&gt;&lt;/h3&gt;
&lt;div class="prose-full-illustration"&gt;
&lt;img src="https://eev.ee/media/gamedev-from-scratch/pico8-editor-sound-pitch.png" alt="A very small sound editor, showing a sound as bars representing pitch"&gt;
&lt;img src="https://eev.ee/media/gamedev-from-scratch/pico8-editor-sound-tracker.png" alt="The same sound, but shown using a tracker-like interface"&gt;
&lt;/div&gt;

&lt;p&gt;The sound editor (or &lt;span class="caps"&gt;SFX&lt;/span&gt; editor) does a lot, despite being very simple conceptually, and it can be a little intimidating if you&amp;#8217;ve never worked with sound or music before.  These screenshots are the two display modes, &amp;#8220;pitch mode&amp;#8221; and &amp;#8220;tracker mode&amp;#8221; — allegedly pitch mode is more suitable for sound effects and tracker mode is more suitable for music, but I honestly have no idea how anyone does anything in pitch mode, and I use tracker mode for both.  Your mileage may vary.  As with the map editor, use &lt;kbd&gt;Tab&lt;/kbd&gt; or the buttons in the top-left to switch&amp;nbsp;views.&lt;/p&gt;
&lt;p&gt;There are 64 sound effects to work with, each consisting of 32 notes played by a little chiptune synth.  Notes consist of a pitch (i.e., the actual note being played), an instrument, the volume, and an optional&amp;nbsp;effect.&lt;/p&gt;
&lt;p&gt;I could say an awful lot about sound and chiptunes and what any of this means, but this is not a chiptuning tutorial, so I&amp;#8217;ll save that for when I actually made some sounds for the game.  Do feel free to mess around here,&amp;nbsp;though.&lt;/p&gt;
&lt;p&gt;There&amp;#8217;s also a music editor, but all it does is arrange several sound effects to play at the same time, so it&amp;#8217;s not especially&amp;nbsp;interesting.&lt;/p&gt;
&lt;p&gt;And that&amp;#8217;s everything at my disposal!  I guess that means it&amp;#8217;s time to get started, for real.  Go back to the command prompt and use &lt;code&gt;reboot&lt;/code&gt; to get a fresh blank cartridge, if you&amp;#8217;re planning on following&amp;nbsp;along.&lt;/p&gt;
&lt;h2 id="inspiration"&gt;&lt;a class="toclink" href="#inspiration"&gt;Inspiration&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;The first step to making a game is having a game you want to&amp;nbsp;make.&lt;/p&gt;
&lt;p&gt;I started on this at the end of April, after a very rushed month spent preparing the &lt;a href="https://eev.ee/release/2020/11/30/cherry-kisses-on-steam/"&gt;Steam release of Cherry Kisses&lt;/a&gt;.  I was pretty pumped about having just published something in a very visible place for the first time, and I wanted to keep that energy going, but I didn&amp;#8217;t want to immediately jump into an even larger thing.  I wanted to make something small, something self-contained, something I could do entirely on my own.  (My spouse is the better artist by far, and they did all the art for Cherry&amp;nbsp;Kisses.)&lt;/p&gt;
&lt;p&gt;The &lt;span class="caps"&gt;PICO&lt;/span&gt;-8 came to mind as the obvious platform to use.  For one, the limitations make it very difficult for a game&amp;#8217;s scope to balloon very far; you will simply run out of space and &lt;em&gt;have&lt;/em&gt; to cut some ideas.  For two, the art and audio are fairly low-resolution, so I wouldn&amp;#8217;t have much opportunity to endlessly fuss over trying to make them perfect.  For three, it runs in a browser, even on phones, so the resulting game would be easy for anyone to play.  (Having to download a thing will discourage a surprising amount of casual passersby, especially if the thing is fairly small and thus&amp;nbsp;low-reward.)&lt;/p&gt;
&lt;aside class="aside--well-actually"&gt;
&lt;p&gt;It's possible to make a PICO-8 game that spans multiple cartridges, so a game could be arbitrarily large!  But I've never tried it and am never going to, because having a hard cap on how much stuff I can do is much of the appeal of PICO-8 for&amp;nbsp;me.&lt;/p&gt;
&lt;/aside&gt;
&lt;p&gt;I also just find the &lt;span class="caps"&gt;PICO&lt;/span&gt;-8 endlessly charming, and I hadn&amp;#8217;t touched it in a couple years and was curious how it had improved in the interim.  It&amp;#8217;s great for a game started on a whim, too, since I can jump in and start slapping stuff on the screen without worrying that my &lt;span class="caps"&gt;ADHD&lt;/span&gt; brain will start fretting over how everything should be&amp;nbsp;organized.&lt;/p&gt;
&lt;p&gt;That only left the question of &lt;em&gt;what&lt;/em&gt; to&amp;nbsp;make.&lt;/p&gt;
&lt;p&gt;Two and a half years prior — almost three, now — I&amp;#8217;d started on a platformer where you played as Star Anise, my cat&amp;#8217;s fursona.  It was intended to be a goofy Metroidvania where you collected cat-themed powers, ran around defeating little monsters, collected useless garbage, and generally left a trail of minor mayhem in your wake.  Sadly, it was interrupted by real-life events and we haven&amp;#8217;t touched it&amp;nbsp;since.&lt;/p&gt;
&lt;div class="prose-full-illustration"&gt;
&lt;img src="https://eev.ee/media/gamedev-from-scratch/star-anise-original-sample.gif" alt="A clip of a pastel game where a small cat meows loudly and shoots a bubble gun that knocks jars off of shelves."&gt;
&lt;/div&gt;

&lt;p&gt;I loved how this game was shaping up!  It was so goofy, but its goofiness really opened up the design.  Star Anise is great to build a game around.  I can give him all manner of strong yet absurd motivations, and as long as I tie them to something vaguely cat-themed, they&amp;#8217;ll be memorable and feel sensible.  I can load him up with goofy cat-themed powers without needing any kind of justification, because he&amp;#8217;s a cat, and everyone knows cats are basically magic anyway.  He has a group of friends already built in: other cats.  And most importantly, he&amp;#8217;s just fun to play as, because everything he does is ridiculous and overboard, but you never have to feel guilty about his mischief because he&amp;#8217;s a&amp;nbsp;cat.&lt;/p&gt;
&lt;p&gt;It&amp;#8217;s such a good hook.  I&amp;#8217;ve wanted to make a whole series of little Star Anise games, but the furthest I&amp;#8217;d gotten so far was &lt;a href="https://eevee.itch.io/anise-escape-despair"&gt;Star Anise Chronicles: Escape from the Chamber of Despair&lt;/a&gt; — which is good, but is also a text adventure, one of the most impenetrable genres&amp;nbsp;imaginable.&lt;/p&gt;
&lt;p&gt;So why not take another crack at it?  I couldn&amp;#8217;t fit the entire original vision into a &lt;span class="caps"&gt;PICO&lt;/span&gt;-8 game, but surely I&amp;#8217;d have enough room for Star Anise, a few of the abilities we&amp;#8217;d come up with, and some things to interact with.  At long last, a Star Anise&amp;nbsp;platformer.&lt;/p&gt;
&lt;p&gt;You could say the stars aligned.  The stars.  Get it?  Like Star Anise.&amp;nbsp;Okay.&lt;/p&gt;
&lt;h2 id="from-zero-to-something"&gt;&lt;a class="toclink" href="#from-zero-to-something"&gt;From zero to&amp;nbsp;something&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Before I could do anything, I needed some art.  Okay, that&amp;#8217;s not true; I could have boxes moving around on the screen, but I&amp;#8217;ve done this enough that I am beyond tired of boxes.  If I&amp;#8217;m gonna make a Star Anise game then I want to have Star Anise on the damn screen right from the&amp;nbsp;start.&lt;/p&gt;
&lt;p&gt;And right away I had to make some decisions.  I wanted this to be a &lt;em&gt;little bit&lt;/em&gt; Metroidvania style, where Star Anise gained his handful of powers throughout the game and could then explore new&amp;nbsp;areas.&lt;/p&gt;
&lt;p&gt;That meant I wanted as much map space as humanly possible, so from the very beginning I knew the sprite/map split I wanted: all map.  32 screens, but only 128&amp;nbsp;sprites.&lt;/p&gt;
&lt;p&gt;And that made several other decisions, automatically.  I probably wouldn&amp;#8217;t have enough sprite space to include a gun and enemies and whatnot, but a puzzler would let me skip all of&amp;nbsp;that.&lt;/p&gt;
&lt;p&gt;This is why I chose &lt;span class="caps"&gt;PICO&lt;/span&gt;-8!  The game basically decided its own design with only minimal input from me.  Puzzle platformer with some&amp;nbsp;powerups.&lt;/p&gt;
&lt;p&gt;Now, to draw Star Anise, which meant deciding how big he should be.  A very conspicuous part of his design is his huge helmet, which wouldn&amp;#8217;t fit especially well in a single 8×8 tile, or even in two of them stacked.  I decided to go one bigger and make a 2×3&amp;nbsp;block.&lt;/p&gt;
&lt;div class="prose-full-illustration"&gt;
&lt;img src="https://eev.ee/media/gamedev-from-scratch/v1-sprite.png" alt="A charming little Star Anise sprite, with some extra bits next to him"&gt;
&lt;/div&gt;

&lt;p&gt;This wasn&amp;#8217;t especially complicated to draw.  At this size, it feels like a lot of the sprites draw themselves, too.  It did help that I&amp;#8217;d already seen my spouse&amp;#8217;s interpretation of Star Anise from the prototype game above, but I think the general lesson there is to look at existing art that&amp;#8217;s similar to what you want to draw and reverse-engineer the bits that make it work.  Here, I made a big circle, squeezed in the narrowest possible face — a pixel each for the eyes, then three pixels for spacing — and gave him a rectangle for his body.  Toss a couple stars into the inside of the helmet and, presto, that&amp;#8217;s Star&amp;nbsp;Anise.&lt;/p&gt;
&lt;p&gt;You might be wondering about those weird extra tiles on the side!  I&amp;#8217;ll get to those in a&amp;nbsp;moment.&lt;/p&gt;
&lt;p&gt;With Star Anise drawn, the obvious first thing is to put him on the dang&amp;nbsp;screen.&lt;/p&gt;
&lt;table class="highlighttable"&gt;&lt;tr&gt;&lt;td class="linenos"&gt;&lt;div class="linenodiv"&gt;&lt;pre&gt;&lt;span class="normal"&gt; 1&lt;/span&gt;
&lt;span class="normal"&gt; 2&lt;/span&gt;
&lt;span class="normal"&gt; 3&lt;/span&gt;
&lt;span class="normal"&gt; 4&lt;/span&gt;
&lt;span class="normal"&gt; 5&lt;/span&gt;
&lt;span class="normal"&gt; 6&lt;/span&gt;
&lt;span class="normal"&gt; 7&lt;/span&gt;
&lt;span class="normal"&gt; 8&lt;/span&gt;
&lt;span class="normal"&gt; 9&lt;/span&gt;
&lt;span class="normal"&gt;10&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/td&gt;&lt;td class="code"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="kr"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;_init&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="kr"&gt;end&lt;/span&gt;

&lt;span class="kr"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;_update&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="kr"&gt;end&lt;/span&gt;

&lt;span class="kr"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;_draw&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="n"&gt;cls&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="n"&gt;spr&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;64&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;64&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="kr"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;p&gt;Some explanation may be in order.  For starters, a &amp;#8220;function&amp;#8221; is a block of code that can be used repeatedly.  (But then, this is not a programming tutorial.)  These particular functions are special to the &lt;span class="caps"&gt;PICO&lt;/span&gt;-8: &lt;code&gt;_init&lt;/code&gt; runs when the cartridge starts, &lt;code&gt;_update&lt;/code&gt; runs every frame, and &lt;code&gt;_draw&lt;/code&gt; also runs every&amp;nbsp;frame.&lt;/p&gt;
&lt;p&gt;What&amp;#8217;s a frame, you ask?  Well, you know how movies aren&amp;#8217;t &lt;em&gt;really&lt;/em&gt; showing movement, but are more like a very fast slideshow?  Real life is &amp;#8220;continuous&amp;#8221; — that is, events occur smoothly over time, so when an object moves, it goes through every point between where it started and where it ends up.  But we have no way to record that motion in full, becuase that would be an infinite amount of information!  The best we can do is take a lot of snapshots very close together.  And it turns out our eyes also work with snapshots (more or less), so it works well&amp;nbsp;enough.&lt;/p&gt;
&lt;aside class="aside--well-actually"&gt;
&lt;p&gt;Arguably, real life might not be continuous, either.  There's a thing called &lt;em&gt;Planck time&lt;/em&gt;, the shortest possible meaningful unit of time.  As I understand it, the idea is that all other amounts of time are just a (very large) number of Planck times, and the universe also exists as a series of&amp;nbsp;snapshots.&lt;/p&gt;
&lt;/aside&gt;
&lt;p&gt;Likewise, &lt;em&gt;simulating&lt;/em&gt; continuous behavior is extremely difficult, so video games tend to cheat the same way.  We slice time into thin chunks — also called &lt;em&gt;frames&lt;/em&gt; — and during each one, we move everything in the world ahead by that amount of time.  If frames are short enough, you get the illusion that the world is behaving smoothly.  Surprise!  It&amp;#8217;s all&amp;nbsp;fake.&lt;/p&gt;
&lt;aside class="aside--computers-are-bad"&gt;
&lt;p&gt;The use of the word “frame” becomes incredibly confusing when dealing with sprite animation, which is also composed of frames (in the movie sense), but those frames may last varying amounts of time, including for multiple frames (in the game sense).  You sometimes hear awful phrases like “a three-frame frame” or something.  I am sorry on behalf of all programmers, who cannot help but reuse words in this&amp;nbsp;way.&lt;/p&gt;
&lt;/aside&gt;
&lt;p&gt;Modern games can (or should) deal with a varying &lt;em&gt;frame rate&lt;/em&gt;, where each frame is a slightly (or greatly) different duration for any of myriad reasons.  Since the &lt;span class="caps"&gt;PICO&lt;/span&gt;-8 is a faux-retro console, I&amp;#8217;ll be using the retro term &lt;em&gt;tic&lt;/em&gt;.  It means the same thing, but it&amp;#8217;s sometimes used for older systems where the framerate is reliably fixed, usually because it&amp;#8217;s tied to (or even enforced by) hardware somewhere.  Here it&amp;#8217;s just emulated, but, you know, close&amp;nbsp;enough.&lt;/p&gt;
&lt;p&gt;Right, so, back to the &lt;span class="caps"&gt;PICO&lt;/span&gt;-8 itself.  Every tic (of which there are 30 per second), the &lt;span class="caps"&gt;PICO&lt;/span&gt;-8 does two things: it calls &lt;code&gt;_update&lt;/code&gt; to advance the game, then it calls &lt;code&gt;_draw&lt;/code&gt; to draw the new state of the game to the screen.  You might immediately wonder: why have these be separate if they happen one after the other anyway?  Great question!  The answer is that the &lt;span class="caps"&gt;PICO&lt;/span&gt;-8 does something clever — if it notices that the &lt;code&gt;_update&lt;/code&gt; + &lt;code&gt;_draw&lt;/code&gt; combination is taking longer than one tic (and the game is thus starting to lag), it will automatically drop down to 15 &lt;span class="caps"&gt;FPS&lt;/span&gt;.  In this mode, it will call &lt;code&gt;_update&lt;/code&gt; &lt;em&gt;twice&lt;/em&gt; and then call &lt;code&gt;_draw&lt;/code&gt;.  Here is a terrible &lt;span class="caps"&gt;ASCII&lt;/span&gt;&amp;nbsp;diagram.&lt;/p&gt;
&lt;table class="highlighttable"&gt;&lt;tr&gt;&lt;td class="linenos"&gt;&lt;div class="linenodiv"&gt;&lt;pre&gt;&lt;span class="normal"&gt;1&lt;/span&gt;
&lt;span class="normal"&gt;2&lt;/span&gt;
&lt;span class="normal"&gt;3&lt;/span&gt;
&lt;span class="normal"&gt;4&lt;/span&gt;
&lt;span class="normal"&gt;5&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/td&gt;&lt;td class="code"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;        | tic                   | tic                   |
--------+-----------------------+-----------------------+
30 FPS: | _update() _draw()     | _update() _draw()     |
--------+-----------------------+-----------------------+
15 FPS: | _update() _update() _draw()                   |
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;p&gt;As you can see, the game still updates twice in the same amount of time, so it still &lt;em&gt;runs&lt;/em&gt; at the same speed, but it only draws half as often.  With any luck, that saves enough effort that the game can keep running at the intended&amp;nbsp;speed.&lt;/p&gt;
&lt;p&gt;All of that is to say: the &lt;code&gt;_draw&lt;/code&gt; function draws to the&amp;nbsp;screen.&lt;/p&gt;
&lt;p&gt;The first thing you (&lt;em&gt;usually&lt;/em&gt;) want to do in &lt;code&gt;_draw&lt;/code&gt; is clear the screen, which is accomplished by the charmingly terse &lt;code&gt;cls()&lt;/code&gt;.  If you don&amp;#8217;t do this, your game will merrily draw right on top of whatever was on the screen previously: the prompt, a previous game, even the code&amp;nbsp;editor.&lt;/p&gt;
&lt;p&gt;After that, I called &lt;code&gt;spr()&lt;/code&gt; to draw Star Anise.  The usual arguments are &lt;code&gt;spr(n, x, y)&lt;/code&gt;, where &lt;code&gt;n&lt;/code&gt; is the sprite number (visible near the middle of the screen in the sprite editor) and &lt;code&gt;x, y&lt;/code&gt; say where to place him.  He&amp;#8217;s made up of six tiles, and you might think that drawing six tiles would thus require calling &lt;code&gt;spr()&lt;/code&gt; six times, but it helpfully takes two more optional arguments: &lt;em&gt;how many&lt;/em&gt; tiles to draw, as a single rectangle taken from the spritesheet.  The above code thus draws a 2-by-3 block of tiles, starting from tile 1, at the coordinates (64, 64) — the center of the&amp;nbsp;screen.&lt;/p&gt;
&lt;p&gt;As is programming tradition, sprites are drawn from their top-left corner, so the initial tile is the top-left of the rectangle that gets drawn, and the coordinates are where the top-left of the drawn rectangle appears on screen.  Thus, Star Anise appears with his top left &amp;#8220;corner&amp;#8221; in the middle of the&amp;nbsp;screen.&lt;/p&gt;
&lt;div class="prose-full-illustration"&gt;
&lt;img src="https://eev.ee/media/gamedev-from-scratch/v1-anise.png" alt="Star Anise standing near the middle of the screen, as promised"&gt;
&lt;/div&gt;

&lt;p&gt;There he is!  How immensely satisfying.  I always try to get &lt;em&gt;something&lt;/em&gt; &amp;#8220;real&amp;#8221; drawing as early as humanly possible.  It helps me feel like I&amp;#8217;ve made some progress, like I&amp;#8217;m working on a specific game and have made steps towards making it exist.  This is already, quite clearly, a Star Anise game, but that wouldn&amp;#8217;t be obvious if I&amp;#8217;d started out with&amp;nbsp;rectangles.&lt;/p&gt;
&lt;p&gt;Now what?  A good start would be to have him move around a bit.  That&amp;#8217;s easy enough if I introduce some&amp;nbsp;state.&lt;/p&gt;
&lt;p&gt;I do need to check what buttons the player is pressing, which I can do with &lt;code&gt;btn(b)&lt;/code&gt;, where &lt;code&gt;b&lt;/code&gt; is the button&amp;#8230;  number.  Left is button 0, right is button 1, up is button 2&amp;#8230;  but that makes for some unreadable garbage, so instead, let&amp;#8217;s use a recently-introduced shortcut.  If you hold &lt;kbd&gt;Shift&lt;/kbd&gt; and press &lt;kbd&gt;U&lt;/kbd&gt;, &lt;kbd&gt;D&lt;/kbd&gt;, &lt;kbd&gt;L&lt;/kbd&gt;, &lt;kbd&gt;R&lt;/kbd&gt;, &lt;kbd&gt;O&lt;/kbd&gt;, or &lt;kbd&gt;X&lt;/kbd&gt;, the &lt;span class="caps"&gt;PICO&lt;/span&gt;-8 will insert a symbol representing that button.  (I will be representing those symbols as ⬆️⬇️⬅️➡️🅾️❎, which is how the &lt;span class="caps"&gt;PICO&lt;/span&gt;-8 stores them on&amp;nbsp;disk.)&lt;/p&gt;
&lt;p&gt;That&amp;#8217;s enough to move him&amp;nbsp;around:&lt;/p&gt;
&lt;table class="highlighttable"&gt;&lt;tr&gt;&lt;td class="linenos"&gt;&lt;div class="linenodiv"&gt;&lt;pre&gt;&lt;span class="normal"&gt; 1&lt;/span&gt;
&lt;span class="normal"&gt; 2&lt;/span&gt;
&lt;span class="normal"&gt; 3&lt;/span&gt;
&lt;span class="normal"&gt; 4&lt;/span&gt;
&lt;span class="normal"&gt; 5&lt;/span&gt;
&lt;span class="normal"&gt; 6&lt;/span&gt;
&lt;span class="normal"&gt; 7&lt;/span&gt;
&lt;span class="normal"&gt; 8&lt;/span&gt;
&lt;span class="normal"&gt; 9&lt;/span&gt;
&lt;span class="normal"&gt;10&lt;/span&gt;
&lt;span class="normal"&gt;11&lt;/span&gt;
&lt;span class="normal"&gt;12&lt;/span&gt;
&lt;span class="normal"&gt;13&lt;/span&gt;
&lt;span class="normal"&gt;14&lt;/span&gt;
&lt;span class="normal"&gt;15&lt;/span&gt;
&lt;span class="normal"&gt;16&lt;/span&gt;
&lt;span class="normal"&gt;17&lt;/span&gt;
&lt;span class="normal"&gt;18&lt;/span&gt;
&lt;span class="normal"&gt;19&lt;/span&gt;
&lt;span class="normal"&gt;20&lt;/span&gt;
&lt;span class="normal"&gt;21&lt;/span&gt;
&lt;span class="normal"&gt;22&lt;/span&gt;
&lt;span class="normal"&gt;23&lt;/span&gt;
&lt;span class="normal"&gt;24&lt;/span&gt;
&lt;span class="normal"&gt;25&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/td&gt;&lt;td class="code"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="kr"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;_init&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="kr"&gt;end&lt;/span&gt;

&lt;span class="kd"&gt;local&lt;/span&gt; &lt;span class="n"&gt;px&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;64&lt;/span&gt;
&lt;span class="kd"&gt;local&lt;/span&gt; &lt;span class="n"&gt;py&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;64&lt;/span&gt;

&lt;span class="kr"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;_update&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="kr"&gt;if&lt;/span&gt; &lt;span class="n"&gt;btn&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="err"&gt;⬆️&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="kr"&gt;then&lt;/span&gt;
        &lt;span class="n"&gt;py&lt;/span&gt; &lt;span class="o"&gt;-=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
    &lt;span class="kr"&gt;end&lt;/span&gt;
    &lt;span class="kr"&gt;if&lt;/span&gt; &lt;span class="n"&gt;btn&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="err"&gt;⬇️&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="kr"&gt;then&lt;/span&gt;
        &lt;span class="n"&gt;py&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
    &lt;span class="kr"&gt;end&lt;/span&gt;
    &lt;span class="kr"&gt;if&lt;/span&gt; &lt;span class="n"&gt;btn&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="err"&gt;⬅️&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="kr"&gt;then&lt;/span&gt;
        &lt;span class="n"&gt;px&lt;/span&gt; &lt;span class="o"&gt;-=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
    &lt;span class="kr"&gt;end&lt;/span&gt;
    &lt;span class="kr"&gt;if&lt;/span&gt; &lt;span class="n"&gt;btn&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="err"&gt;➡️&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="kr"&gt;then&lt;/span&gt;
        &lt;span class="n"&gt;px&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
    &lt;span class="kr"&gt;end&lt;/span&gt;
&lt;span class="kr"&gt;end&lt;/span&gt;

&lt;span class="kr"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;_draw&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="n"&gt;cls&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="n"&gt;spr&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;px&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;py&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="kr"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;p&gt;Here I&amp;#8217;ve put his position (still anchored at his top-left) into some variables, and during &lt;code&gt;_update()&lt;/code&gt; I update them.  (If you&amp;#8217;re familiar with Lua, you may balk at &lt;code&gt;+=&lt;/code&gt; and &lt;code&gt;-=&lt;/code&gt; — these are extensions added by &lt;span class="caps"&gt;PICO&lt;/span&gt;-8, and they save enough space that they&amp;#8217;re definitely worth&amp;nbsp;it.)&lt;/p&gt;
&lt;aside class="aside--look-out"&gt;
&lt;p&gt;This code technically has a bug already.  It looks like Star Anise is meant to move at 1 pixel per tic, but if you hold two directions at once, he'll move 1 pixel horizontally &lt;em&gt;and&lt;/em&gt; one pixel vertically, for a combined velocity of &lt;span class="math"&gt;\(\sqrt{2} \approx 1.4\)&lt;/span&gt; pixels per tic.  That's 40% faster than&amp;nbsp;intended!&lt;/p&gt;
&lt;p&gt;This is a common mistake (and a crucial &lt;a href="https://doomwiki.org/wiki/Straferunning"&gt;speedrunning technique in classic Doom&lt;/a&gt;), but it doesn't matter here — remember, I'm making a &lt;em&gt;platformer&lt;/em&gt;, so the vertical movement will go away as soon as I have gravity and something for him to stand&amp;nbsp;on.&lt;/p&gt;
&lt;/aside&gt;
&lt;aside class="aside--look-out"&gt;
&lt;p&gt;There's &lt;em&gt;also&lt;/em&gt; a problem with units here.  Star Anise's velocity is 1 pixel/tic, a speed.  But &lt;code&gt;px&lt;/code&gt; is a position, measured in plain pixels.  You can't meaningfully add a speed to a position.  What's happening here is that this code runs once per tic, so it's implicitly multiplying by 1 tic to get 1 pixel, then adding&amp;nbsp;that.&lt;/p&gt;
&lt;p&gt;For any other game system, where fractions are easier to deal with and the framerate isn't fixed in stone, I'd probably measure speed in pixels &lt;em&gt;per second&lt;/em&gt; (which is much easier to reason about) and have access to a &lt;code&gt;dt&lt;/code&gt; value indicating how much time has passed since last frame.  Then I could multiply those to get the distance travelled.  Here, though, my life is easier if the numbers are integers, and multiplying by time would mean tacking on &lt;code&gt;* 1&lt;/code&gt;.  That eats valuable tokens and is more likely to add confusion than clarity, so I've left it&amp;nbsp;off.&lt;/p&gt;
&lt;/aside&gt;
&lt;aside class="aside--computers-are-bad"&gt;
&lt;p&gt;It feels a bit weird to initialize stuff &lt;em&gt;outside&lt;/em&gt; of &lt;code&gt;_init&lt;/code&gt;, the special PICO-8 function that runs when the cartridge first starts.  (Code outside a function also runs when the cartridge starts.  Earlier, in fact!  It &lt;em&gt;has&lt;/em&gt; to, because the &lt;code&gt;function _init()&lt;/code&gt; block is what creates the &lt;code&gt;_init&lt;/code&gt; function in the first&amp;nbsp;place.)&lt;/p&gt;
&lt;p&gt;The reason is, mostly, &lt;code&gt;local&lt;/code&gt;.  Lua's &lt;code&gt;local&lt;/code&gt; keyword makes a variable that only exists within the block; without it, variables are &lt;em&gt;global&lt;/em&gt; and exist everywhere in your entire program.  But if I used it within &lt;code&gt;_init&lt;/code&gt;, then &lt;code&gt;px&lt;/code&gt; and &lt;code&gt;py&lt;/code&gt; would only exist...  within &lt;code&gt;_init&lt;/code&gt;.  I'd have to declare them with &lt;code&gt;local&lt;/code&gt; outside and then assign them inside, and at that point I'm repeating myself for no&amp;nbsp;reason.&lt;/p&gt;
&lt;p&gt;So why use it at all, for globals?  Three reasons.  One, it's a good habit to get into with Lua so you don't accidentally make globals in the wrong place.  Two, it clearly marks where a variable was intended to be &lt;em&gt;created&lt;/em&gt;; if I only wrote &lt;code&gt;px = 64&lt;/code&gt;, that could also mean I think I'm setting an &lt;em&gt;existing&lt;/em&gt; variable called &lt;code&gt;px&lt;/code&gt;, and using &lt;code&gt;local&lt;/code&gt; avoids that ambiguity.  And three, it's very slightly faster for complicated&amp;nbsp;reasons.&lt;/p&gt;
&lt;p&gt;That said, my insistence on &lt;code&gt;local&lt;/code&gt; everywhere does create a couple small problems later on, since a &lt;code&gt;local&lt;/code&gt; variable only exists from that line&amp;nbsp;onwards.&lt;/p&gt;
&lt;/aside&gt;
&lt;div class="prose-full-illustration"&gt;
&lt;img src="https://eev.ee/media/gamedev-from-scratch/v1-anise-moving.gif" alt="Star Anise sliding around the screen"&gt;
&lt;/div&gt;

&lt;p&gt;This is already halfway to being a game — it does something when I press buttons!  Excellent.  But also weird.  This doesn&amp;#8217;t look like Star Anise is walking around; it looks like he&amp;#8217;s a static image being dragged by an invisible cursor or something.  A very easy aesthetic improvement would be to make him not moonwalk when moving&amp;nbsp;left.&lt;/p&gt;
&lt;p&gt;That&amp;#8217;s easy enough; the &lt;code&gt;spr()&lt;/code&gt; function takes two more optional arguments, indicating whether to flip the sprite horizontally and/or vertically.  I can just slap those in when he&amp;#8217;s moving left.  Or, well, not &lt;em&gt;quite&lt;/em&gt; — I want to flip him when the &lt;em&gt;last direction he moved&lt;/em&gt; was left.  If he moves left and then stops, or moves left and then up and down, he should still be facing&amp;nbsp;left.&lt;/p&gt;
&lt;table class="highlighttable"&gt;&lt;tr&gt;&lt;td class="linenos"&gt;&lt;div class="linenodiv"&gt;&lt;pre&gt;&lt;span class="normal"&gt; 1&lt;/span&gt;
&lt;span class="normal"&gt; 2&lt;/span&gt;
&lt;span class="normal"&gt; 3&lt;/span&gt;
&lt;span class="normal"&gt; 4&lt;/span&gt;
&lt;span class="normal"&gt; 5&lt;/span&gt;
&lt;span class="normal"&gt; 6&lt;/span&gt;
&lt;span class="normal"&gt; 7&lt;/span&gt;
&lt;span class="normal"&gt; 8&lt;/span&gt;
&lt;span class="normal"&gt; 9&lt;/span&gt;
&lt;span class="normal"&gt;10&lt;/span&gt;
&lt;span class="normal"&gt;11&lt;/span&gt;
&lt;span class="normal"&gt;12&lt;/span&gt;
&lt;span class="normal"&gt;13&lt;/span&gt;
&lt;span class="normal"&gt;14&lt;/span&gt;
&lt;span class="normal"&gt;15&lt;/span&gt;
&lt;span class="normal"&gt;16&lt;/span&gt;
&lt;span class="normal"&gt;17&lt;/span&gt;
&lt;span class="normal"&gt;18&lt;/span&gt;
&lt;span class="normal"&gt;19&lt;/span&gt;
&lt;span class="normal"&gt;20&lt;/span&gt;
&lt;span class="normal"&gt;21&lt;/span&gt;
&lt;span class="normal"&gt;22&lt;/span&gt;
&lt;span class="normal"&gt;23&lt;/span&gt;
&lt;span class="normal"&gt;24&lt;/span&gt;
&lt;span class="normal"&gt;25&lt;/span&gt;
&lt;span class="normal"&gt;26&lt;/span&gt;
&lt;span class="normal"&gt;27&lt;/span&gt;
&lt;span class="normal"&gt;28&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/td&gt;&lt;td class="code"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="kr"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;_init&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="kr"&gt;end&lt;/span&gt;

&lt;span class="kd"&gt;local&lt;/span&gt; &lt;span class="n"&gt;px&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;64&lt;/span&gt;
&lt;span class="kd"&gt;local&lt;/span&gt; &lt;span class="n"&gt;py&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;64&lt;/span&gt;
&lt;span class="kd"&gt;local&lt;/span&gt; &lt;span class="n"&gt;left&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;

&lt;span class="kr"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;_update&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="kr"&gt;if&lt;/span&gt; &lt;span class="n"&gt;btn&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="err"&gt;⬆️&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="kr"&gt;then&lt;/span&gt;
        &lt;span class="n"&gt;py&lt;/span&gt; &lt;span class="o"&gt;-=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
    &lt;span class="kr"&gt;end&lt;/span&gt;
    &lt;span class="kr"&gt;if&lt;/span&gt; &lt;span class="n"&gt;btn&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="err"&gt;⬇️&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="kr"&gt;then&lt;/span&gt;
        &lt;span class="n"&gt;py&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
    &lt;span class="kr"&gt;end&lt;/span&gt;
    &lt;span class="kr"&gt;if&lt;/span&gt; &lt;span class="n"&gt;btn&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="err"&gt;⬅️&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="kr"&gt;then&lt;/span&gt;
        &lt;span class="n"&gt;px&lt;/span&gt; &lt;span class="o"&gt;-=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
        &lt;span class="n"&gt;left&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
    &lt;span class="kr"&gt;end&lt;/span&gt;
    &lt;span class="kr"&gt;if&lt;/span&gt; &lt;span class="n"&gt;btn&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="err"&gt;➡️&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="kr"&gt;then&lt;/span&gt;
        &lt;span class="n"&gt;px&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
        &lt;span class="n"&gt;left&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;
    &lt;span class="kr"&gt;end&lt;/span&gt;
&lt;span class="kr"&gt;end&lt;/span&gt;

&lt;span class="kr"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;_draw&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="n"&gt;cls&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="n"&gt;spr&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;px&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;py&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;left&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="kr"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;div class="prose-full-illustration"&gt;
&lt;img src="https://eev.ee/media/gamedev-from-scratch/v1-anise-moving2.gif" alt="Star Anise sliding around the screen, but turning around when moving left"&gt;
&lt;/div&gt;

&lt;p&gt;Making progress, but obviously he&amp;#8217;d look a lot better if he were animated,&amp;nbsp;right?&lt;/p&gt;
&lt;p&gt;Which, finally, brings us back to those extra tiles I drew.  They&amp;#8217;re copies of Star Anise&amp;#8217;s legs and antenna, lightly edited to look like he&amp;#8217;s in mid-step.  The legs are sticking out all the way, and the antenna is adjusted to be&amp;#8230;  positioned slightly differently, since it&amp;#8217;s bouncy.  It&amp;#8217;s a bit rough, but I can touch it up&amp;nbsp;later.&lt;/p&gt;
&lt;div class="prose-full-illustration"&gt;
&lt;img src="https://eev.ee/media/gamedev-from-scratch/v1-anise-walk.gif" alt="Star Anise's walk animation"&gt;
&lt;/div&gt;

&lt;p&gt;Note that I&amp;#8217;ve crammed as much movement into as little space as possible here.  This is only a two-frame animation, so the leg movement is exaggerated to get the most bang for my buck.  I don&amp;#8217;t even duplicate the entirety of Star Anise for the other frame; instead, I only copied the tiles that change.  That&amp;#8217;ll make him more complicated to draw, but it does save me sprite space — remember, I only have 127 tiles available, and 9 of them is already 7% gone.  (Writing more code to save on limited asset space is, in my experience, a pretty common &lt;span class="caps"&gt;PICO&lt;/span&gt;-8&amp;nbsp;tactic.)&lt;/p&gt;
&lt;p&gt;Unfortunately, this makes flipping his sprite somewhat more complicated.  I can&amp;#8217;t just use that argument to &lt;code&gt;spr()&lt;/code&gt;, because—  well, I&amp;#8217;ll get to that in a second.  Here&amp;#8217;s the updated&amp;nbsp;code.&lt;/p&gt;
&lt;table class="highlighttable"&gt;&lt;tr&gt;&lt;td class="linenos"&gt;&lt;div class="linenodiv"&gt;&lt;pre&gt;&lt;span class="normal"&gt; 1&lt;/span&gt;
&lt;span class="normal"&gt; 2&lt;/span&gt;
&lt;span class="normal"&gt; 3&lt;/span&gt;
&lt;span class="normal"&gt; 4&lt;/span&gt;
&lt;span class="normal"&gt; 5&lt;/span&gt;
&lt;span class="normal"&gt; 6&lt;/span&gt;
&lt;span class="normal"&gt; 7&lt;/span&gt;
&lt;span class="normal"&gt; 8&lt;/span&gt;
&lt;span class="normal"&gt; 9&lt;/span&gt;
&lt;span class="normal"&gt;10&lt;/span&gt;
&lt;span class="normal"&gt;11&lt;/span&gt;
&lt;span class="normal"&gt;12&lt;/span&gt;
&lt;span class="normal"&gt;13&lt;/span&gt;
&lt;span class="normal"&gt;14&lt;/span&gt;
&lt;span class="normal"&gt;15&lt;/span&gt;
&lt;span class="normal"&gt;16&lt;/span&gt;
&lt;span class="normal"&gt;17&lt;/span&gt;
&lt;span class="normal"&gt;18&lt;/span&gt;
&lt;span class="normal"&gt;19&lt;/span&gt;
&lt;span class="normal"&gt;20&lt;/span&gt;
&lt;span class="normal"&gt;21&lt;/span&gt;
&lt;span class="normal"&gt;22&lt;/span&gt;
&lt;span class="normal"&gt;23&lt;/span&gt;
&lt;span class="normal"&gt;24&lt;/span&gt;
&lt;span class="normal"&gt;25&lt;/span&gt;
&lt;span class="normal"&gt;26&lt;/span&gt;
&lt;span class="normal"&gt;27&lt;/span&gt;
&lt;span class="normal"&gt;28&lt;/span&gt;
&lt;span class="normal"&gt;29&lt;/span&gt;
&lt;span class="normal"&gt;30&lt;/span&gt;
&lt;span class="normal"&gt;31&lt;/span&gt;
&lt;span class="normal"&gt;32&lt;/span&gt;
&lt;span class="normal"&gt;33&lt;/span&gt;
&lt;span class="normal"&gt;34&lt;/span&gt;
&lt;span class="normal"&gt;35&lt;/span&gt;
&lt;span class="normal"&gt;36&lt;/span&gt;
&lt;span class="normal"&gt;37&lt;/span&gt;
&lt;span class="normal"&gt;38&lt;/span&gt;
&lt;span class="normal"&gt;39&lt;/span&gt;
&lt;span class="normal"&gt;40&lt;/span&gt;
&lt;span class="normal"&gt;41&lt;/span&gt;
&lt;span class="normal"&gt;42&lt;/span&gt;
&lt;span class="normal"&gt;43&lt;/span&gt;
&lt;span class="normal"&gt;44&lt;/span&gt;
&lt;span class="normal"&gt;45&lt;/span&gt;
&lt;span class="normal"&gt;46&lt;/span&gt;
&lt;span class="normal"&gt;47&lt;/span&gt;
&lt;span class="normal"&gt;48&lt;/span&gt;
&lt;span class="normal"&gt;49&lt;/span&gt;
&lt;span class="normal"&gt;50&lt;/span&gt;
&lt;span class="normal"&gt;51&lt;/span&gt;
&lt;span class="normal"&gt;52&lt;/span&gt;
&lt;span class="normal"&gt;53&lt;/span&gt;
&lt;span class="normal"&gt;54&lt;/span&gt;
&lt;span class="normal"&gt;55&lt;/span&gt;
&lt;span class="normal"&gt;56&lt;/span&gt;
&lt;span class="normal"&gt;57&lt;/span&gt;
&lt;span class="normal"&gt;58&lt;/span&gt;
&lt;span class="normal"&gt;59&lt;/span&gt;
&lt;span class="normal"&gt;60&lt;/span&gt;
&lt;span class="normal"&gt;61&lt;/span&gt;
&lt;span class="normal"&gt;62&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/td&gt;&lt;td class="code"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="kd"&gt;local&lt;/span&gt; &lt;span class="n"&gt;anise_stand&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;17&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;18&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;33&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;34&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="kd"&gt;local&lt;/span&gt; &lt;span class="n"&gt;anise_jump&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;17&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;18&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;19&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;35&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kr"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;_init&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="kr"&gt;end&lt;/span&gt;

&lt;span class="kd"&gt;local&lt;/span&gt; &lt;span class="n"&gt;t&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;
&lt;span class="kd"&gt;local&lt;/span&gt; &lt;span class="n"&gt;px&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;64&lt;/span&gt;
&lt;span class="kd"&gt;local&lt;/span&gt; &lt;span class="n"&gt;py&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;64&lt;/span&gt;
&lt;span class="kd"&gt;local&lt;/span&gt; &lt;span class="n"&gt;left&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;
&lt;span class="kd"&gt;local&lt;/span&gt; &lt;span class="n"&gt;moving&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;

&lt;span class="kr"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;_update&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="n"&gt;t&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
    &lt;span class="n"&gt;t&lt;/span&gt; &lt;span class="o"&gt;%=&lt;/span&gt; &lt;span class="mi"&gt;120&lt;/span&gt;

    &lt;span class="n"&gt;moving&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;
    &lt;span class="kr"&gt;if&lt;/span&gt; &lt;span class="n"&gt;btn&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="err"&gt;⬆️&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="kr"&gt;then&lt;/span&gt;
        &lt;span class="n"&gt;py&lt;/span&gt; &lt;span class="o"&gt;-=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
        &lt;span class="n"&gt;moving&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
    &lt;span class="kr"&gt;end&lt;/span&gt;
    &lt;span class="kr"&gt;if&lt;/span&gt; &lt;span class="n"&gt;btn&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="err"&gt;⬇️&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="kr"&gt;then&lt;/span&gt;
        &lt;span class="n"&gt;py&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
        &lt;span class="n"&gt;moving&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
    &lt;span class="kr"&gt;end&lt;/span&gt;
    &lt;span class="kr"&gt;if&lt;/span&gt; &lt;span class="n"&gt;btn&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="err"&gt;⬅️&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="kr"&gt;then&lt;/span&gt;
        &lt;span class="n"&gt;px&lt;/span&gt; &lt;span class="o"&gt;-=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
        &lt;span class="n"&gt;moving&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
        &lt;span class="n"&gt;left&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
    &lt;span class="kr"&gt;end&lt;/span&gt;
    &lt;span class="kr"&gt;if&lt;/span&gt; &lt;span class="n"&gt;btn&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="err"&gt;➡️&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="kr"&gt;then&lt;/span&gt;
        &lt;span class="n"&gt;px&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
        &lt;span class="n"&gt;moving&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
        &lt;span class="n"&gt;left&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;
    &lt;span class="kr"&gt;end&lt;/span&gt;
&lt;span class="kr"&gt;end&lt;/span&gt;

&lt;span class="kr"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;_draw&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="n"&gt;cls&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

    &lt;span class="kd"&gt;local&lt;/span&gt; &lt;span class="n"&gt;pose&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;anise_stand&lt;/span&gt;
    &lt;span class="kr"&gt;if&lt;/span&gt; &lt;span class="n"&gt;moving&lt;/span&gt; &lt;span class="ow"&gt;and&lt;/span&gt; &lt;span class="n"&gt;t&lt;/span&gt; &lt;span class="o"&gt;%&lt;/span&gt; &lt;span class="mi"&gt;8&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="mi"&gt;4&lt;/span&gt; &lt;span class="kr"&gt;then&lt;/span&gt;
        &lt;span class="n"&gt;pose&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;anise_jump&lt;/span&gt;
    &lt;span class="kr"&gt;end&lt;/span&gt;
    &lt;span class="kd"&gt;local&lt;/span&gt; &lt;span class="n"&gt;y&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;py&lt;/span&gt;
    &lt;span class="kd"&gt;local&lt;/span&gt; &lt;span class="n"&gt;x0&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;px&lt;/span&gt;
    &lt;span class="kd"&gt;local&lt;/span&gt; &lt;span class="n"&gt;dx&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;8&lt;/span&gt;
    &lt;span class="kr"&gt;if&lt;/span&gt; &lt;span class="n"&gt;left&lt;/span&gt; &lt;span class="kr"&gt;then&lt;/span&gt;
        &lt;span class="n"&gt;dx&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;8&lt;/span&gt;
        &lt;span class="n"&gt;x0&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="mi"&gt;8&lt;/span&gt;
    &lt;span class="kr"&gt;end&lt;/span&gt;
    &lt;span class="kd"&gt;local&lt;/span&gt; &lt;span class="n"&gt;x&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;x0&lt;/span&gt;
    &lt;span class="kr"&gt;for&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;#&lt;/span&gt;&lt;span class="n"&gt;pose&lt;/span&gt; &lt;span class="kr"&gt;do&lt;/span&gt;
        &lt;span class="n"&gt;spr&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;pose&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;y&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;left&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="kr"&gt;if&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="o"&gt;%&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt; &lt;span class="kr"&gt;then&lt;/span&gt;
            &lt;span class="n"&gt;x&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;x0&lt;/span&gt;
            &lt;span class="n"&gt;y&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="mi"&gt;8&lt;/span&gt;
        &lt;span class="kr"&gt;else&lt;/span&gt;
            &lt;span class="n"&gt;x&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="n"&gt;dx&lt;/span&gt;
        &lt;span class="kr"&gt;end&lt;/span&gt;
    &lt;span class="kr"&gt;end&lt;/span&gt;
&lt;span class="kr"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;p&gt;That sure got longer in a hurry!  A quick&amp;nbsp;overview:&lt;/p&gt;
&lt;p&gt;I&amp;#8217;ve introduced a global called &lt;code&gt;t&lt;/code&gt; to act as a clock.  I intend to use this for animation and other global cycles, so I don&amp;#8217;t care about the &lt;em&gt;actual&lt;/em&gt; time — that&amp;#8217;s why I take it mod&amp;nbsp;120.&lt;/p&gt;
&lt;p&gt;If you&amp;#8217;re not familiar, the &lt;code&gt;%&lt;/code&gt; (or “modulus”) operator gives you the remainder after division.  It&amp;#8217;s super duper useful and I wish we taught it as a primitive math operation!  You can think of it like &amp;#8220;clock arithmetic&amp;#8221; — if it&amp;#8217;s 9 o&amp;#8217;clock and you wait 4 hours, it becomes 1 o&amp;#8217;clock, which is the remainder when you divide 9 + 4 by 12.  Or you can think of it as removing all chunks of something — to convert the 24-hour &amp;#8220;13 o&amp;#8217;clock&amp;#8221; to 12-hour, you remove all the 12s, leaving just 1 behind.  Or you can think of it as coiling the entire number line into a circle, so after 11 you wrap around to 0 and start over.  (That&amp;#8217;s not quite how clocks work, but using 0–11 turns out to be much simpler than using&amp;nbsp;1–12.)&lt;/p&gt;
&lt;p&gt;The upshot here is that &lt;code&gt;t&lt;/code&gt; will hit 119 and then wrap back around to zero, which is important because &lt;span class="caps"&gt;PICO&lt;/span&gt;-8 numbers can&amp;#8217;t go any higher than 32767.  If I left it to its own devices, it would still wrap around, but to the more cumbersome -32768.  I don&amp;#8217;t want a negative&amp;nbsp;clock!&lt;/p&gt;
&lt;p&gt;But why 120?  Because I want to be able to divide the clock cycle into smaller animation cycles, and I can only do that evenly if the whole clock&amp;#8217;s length is a multiple of the smaller cycle&amp;#8217;s length.  (On a more powerful system, I&amp;#8217;d have a more elaborate animation setup, but that would cost more space and code than I&amp;#8217;m willing to spend here.)  Consider if I had a clock that wrapped around at 10, and I wanted an animation 3 tics long.  I would use modulo 3 to shrink the clock, resulting&amp;nbsp;in:&lt;/p&gt;
&lt;div class="prose-full-illustration"&gt;
&lt;object type="image/svg+xml" data="https://eev.ee/media/gamedev-from-scratch/v1-clock-cycle.svg"&gt;&lt;/object&gt;
&lt;/div&gt;

&lt;p&gt;Whoops!  Frame 0 will show twice in a row, intermittently, even seemingly at random.  That&amp;#8217;s not great.  For the best chance of avoiding that problem without having to think too hard about it, I want a clock whose length is divisible by as much stuff as possible — a &lt;a href="https://en.wikipedia.org/wiki/Highly_composite_number"&gt;highly composite number&lt;/a&gt;.  And, of course, 120 is one such&amp;nbsp;number.&lt;/p&gt;
&lt;aside class="aside--note-from-future"&gt;
&lt;p&gt;It's not an especially good such number, though; it's not even divisible by 7!  I have everything up to 32767 to choose from, so later I will switch to a clock length of 20160.  Not really sure why I don't go with 27720, which is the biggest highly composite number that the PICO-8 can&amp;nbsp;express.&lt;/p&gt;
&lt;p&gt;Oh, and after a few rounds of also using &lt;code&gt;t&lt;/code&gt; as a variable name for a tile id, I'll rename the clock to the much more intuitive...  &lt;code&gt;clock&lt;/code&gt;.&lt;/p&gt;
&lt;/aside&gt;
&lt;p&gt;Next, I track whether Star Anise is moving &lt;em&gt;at all&lt;/em&gt;, so I know whether to play the walk animation.  Note that I always assume he &lt;em&gt;isn&amp;#8217;t&lt;/em&gt; moving, and then correct myself if it turns out he is; otherwise, the new value of &lt;code&gt;moving&lt;/code&gt; would persist into future tics and he&amp;#8217;d never&amp;nbsp;stop.&lt;/p&gt;
&lt;p&gt;That brings me to the new drawing code, which is a little tricky, so here it is a bit at a&amp;nbsp;time:&lt;/p&gt;
&lt;table class="highlighttable"&gt;&lt;tr&gt;&lt;td class="linenos"&gt;&lt;div class="linenodiv"&gt;&lt;pre&gt;&lt;span class="normal"&gt;1&lt;/span&gt;
&lt;span class="normal"&gt;2&lt;/span&gt;
&lt;span class="normal"&gt;3&lt;/span&gt;
&lt;span class="normal"&gt;4&lt;/span&gt;
&lt;span class="normal"&gt;5&lt;/span&gt;
&lt;span class="normal"&gt;6&lt;/span&gt;
&lt;span class="normal"&gt;7&lt;/span&gt;
&lt;span class="normal"&gt;8&lt;/span&gt;
&lt;span class="normal"&gt;9&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/td&gt;&lt;td class="code"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="c1"&gt;-- top of the file&lt;/span&gt;
&lt;span class="kd"&gt;local&lt;/span&gt; &lt;span class="n"&gt;anise_stand&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;17&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;18&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;33&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;34&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="kd"&gt;local&lt;/span&gt; &lt;span class="n"&gt;anise_jump&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;17&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;18&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;19&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;35&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="c1"&gt;-- in _draw()&lt;/span&gt;
    &lt;span class="kd"&gt;local&lt;/span&gt; &lt;span class="n"&gt;pose&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;anise_stand&lt;/span&gt;
    &lt;span class="kr"&gt;if&lt;/span&gt; &lt;span class="n"&gt;moving&lt;/span&gt; &lt;span class="ow"&gt;and&lt;/span&gt; &lt;span class="n"&gt;t&lt;/span&gt; &lt;span class="o"&gt;%&lt;/span&gt; &lt;span class="mi"&gt;8&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="mi"&gt;4&lt;/span&gt; &lt;span class="kr"&gt;then&lt;/span&gt;
        &lt;span class="n"&gt;pose&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;anise_jump&lt;/span&gt;
    &lt;span class="kr"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;p&gt;This decides which tiles I&amp;#8217;m going to draw.  I can&amp;#8217;t draw the walking part (which I&amp;#8217;ve called &amp;#8220;jump&amp;#8221; because it does look like a jump in isolation, and I&amp;#8217;ll be reusing them for that later) as a single block with &lt;code&gt;spr()&lt;/code&gt; like before, and I&amp;#8217;d like to share the code, so both frames are now assembled from individual&amp;nbsp;tiles.&lt;/p&gt;
&lt;p&gt;Note that tiles 1, 2, 17, 18, 33, and 34 are exactly the ones I was drawing in a single &lt;code&gt;spr()&lt;/code&gt; call before.  (The numbers increase by 16 when jumping to the next row, which makes sense, because each row has 16 tiles in it.)  The other set is similar, but it has the alternate tiles substituted&amp;nbsp;in.&lt;/p&gt;
&lt;p&gt;I only want to use the jump tiles if Star Anise is moving, &lt;em&gt;and&lt;/em&gt; if &lt;code&gt;t % 8 &amp;lt; 4&lt;/code&gt;.  That &lt;code&gt;%&lt;/code&gt; turns my 120-tic clock into an 8-tic clock, then checks if we&amp;#8217;re in the first half of it.  Essentially: if it&amp;#8217;s before noon, show the alternate frame; otherwise, show the normal standing&amp;nbsp;frame.&lt;/p&gt;
&lt;div class="prose-full-illustration"&gt;
&lt;object type="image/svg+xml" data="https://eev.ee/media/gamedev-from-scratch/v1-clock-animation.svg"&gt;&lt;/object&gt;
&lt;/div&gt;

&lt;p&gt;The use of a global timer does have some subtle drawbacks here.  If I tap an arrow key to move Star Anise only very briefly, then he may or may not animate, depending on whether the tap happens to be during the &amp;#8220;stand&amp;#8221; or &amp;#8220;jump&amp;#8221; intervals.  A more powerful system, where every animation kept track of its own time, would always briefly show him moving.  (On the other hand, this is an interesting aesthetic in its own right that kinda complements the very low-res and exaggerated&amp;nbsp;animation.)&lt;/p&gt;
&lt;p&gt;Next I need to draw the tiles, but we&amp;#8217;ve come to the catch I mentioned before.  When I draw Star Anise flipped, I&amp;#8217;m now drawing him as a bunch of separate tiles.  If I drew them in the same left-to-right order, then his left side would be flipped, and his right side would be flipped, but the &lt;em&gt;whole image&lt;/em&gt; wouldn&amp;#8217;t be.  Er, just look at this&amp;nbsp;picture.&lt;/p&gt;
&lt;div class="prose-full-illustration"&gt;
&lt;img src="https://eev.ee/media/gamedev-from-scratch/v1-anise-walk-naive-flipped.png" alt="Star Anise's walk frames, flipped one tile at a time"&gt;
&lt;/div&gt;

&lt;p&gt;See?  The tiles are arranged the same way, but each one is &lt;em&gt;individually&lt;/em&gt; flipped, and the result is&amp;#8230;  not what I want.  I&amp;#8217;ll need to also draw the columns in reverse order.  And that&amp;#8217;s exactly what I&amp;nbsp;do:&lt;/p&gt;
&lt;table class="highlighttable"&gt;&lt;tr&gt;&lt;td class="linenos"&gt;&lt;div class="linenodiv"&gt;&lt;pre&gt;&lt;span class="normal"&gt;45&lt;/span&gt;
&lt;span class="normal"&gt;46&lt;/span&gt;
&lt;span class="normal"&gt;47&lt;/span&gt;
&lt;span class="normal"&gt;48&lt;/span&gt;
&lt;span class="normal"&gt;49&lt;/span&gt;
&lt;span class="normal"&gt;50&lt;/span&gt;
&lt;span class="normal"&gt;51&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/td&gt;&lt;td class="code"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;    &lt;span class="kd"&gt;local&lt;/span&gt; &lt;span class="n"&gt;y&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;py&lt;/span&gt;
    &lt;span class="kd"&gt;local&lt;/span&gt; &lt;span class="n"&gt;x0&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;px&lt;/span&gt;
    &lt;span class="kd"&gt;local&lt;/span&gt; &lt;span class="n"&gt;dx&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;8&lt;/span&gt;
    &lt;span class="kr"&gt;if&lt;/span&gt; &lt;span class="n"&gt;left&lt;/span&gt; &lt;span class="kr"&gt;then&lt;/span&gt;
        &lt;span class="n"&gt;dx&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;8&lt;/span&gt;
        &lt;span class="n"&gt;x0&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="mi"&gt;8&lt;/span&gt;
    &lt;span class="kr"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;p&gt;Here I&amp;#8217;m determining the start point and how far apart the tiles are.  The variable names are fairly terse, for a couple of reasons: one, the &lt;span class="caps"&gt;PICO&lt;/span&gt;-8 screen is not very wide, so long variable names make code much harder to read; but also, math code tends to be easier to follow with shorter names anyway.  I&amp;#8217;ve even taken the naming conventions from math — the initial state of a variable is often written with a subscript zero (&lt;span class="math"&gt;\(x_0\)&lt;/span&gt;) and a change is written with the Greek letter delta (&lt;span class="math"&gt;\(\Delta x\)&lt;/span&gt;), so I&amp;#8217;ve used the &lt;span class="caps"&gt;ASCII&lt;/span&gt; equivalents of those, &lt;code&gt;x0&lt;/code&gt; and &lt;code&gt;dx&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;I&amp;#8217;m starting from Star Anise&amp;#8217;s position, of course, and then each tile is 8 pixels right of the previous one&amp;#8230;  if he&amp;#8217;s not flipped.  If he &lt;em&gt;is&lt;/em&gt; flipped, I want to move &lt;em&gt;left&lt;/em&gt;, which will draw the tiles in reverse order.  But that would change where he draws from, so to compensate, I also start drawing 8 pixels right of where I usually would.  (Try to convince yourself that this is correct; on a flipped Star Anise, tile number 1 should draw 8 pixels left from his upper-left&amp;nbsp;corner.)&lt;/p&gt;
&lt;table class="highlighttable"&gt;&lt;tr&gt;&lt;td class="linenos"&gt;&lt;div class="linenodiv"&gt;&lt;pre&gt;&lt;span class="normal"&gt;52&lt;/span&gt;
&lt;span class="normal"&gt;53&lt;/span&gt;
&lt;span class="normal"&gt;54&lt;/span&gt;
&lt;span class="normal"&gt;55&lt;/span&gt;
&lt;span class="normal"&gt;56&lt;/span&gt;
&lt;span class="normal"&gt;57&lt;/span&gt;
&lt;span class="normal"&gt;58&lt;/span&gt;
&lt;span class="normal"&gt;59&lt;/span&gt;
&lt;span class="normal"&gt;60&lt;/span&gt;
&lt;span class="normal"&gt;61&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/td&gt;&lt;td class="code"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;    &lt;span class="kd"&gt;local&lt;/span&gt; &lt;span class="n"&gt;x&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;x0&lt;/span&gt;
    &lt;span class="kr"&gt;for&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;#&lt;/span&gt;&lt;span class="n"&gt;pose&lt;/span&gt; &lt;span class="kr"&gt;do&lt;/span&gt;
        &lt;span class="n"&gt;spr&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;pose&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;y&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;left&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="kr"&gt;if&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="o"&gt;%&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt; &lt;span class="kr"&gt;then&lt;/span&gt;
            &lt;span class="n"&gt;x&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;x0&lt;/span&gt;
            &lt;span class="n"&gt;y&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="mi"&gt;8&lt;/span&gt;
        &lt;span class="kr"&gt;else&lt;/span&gt;
            &lt;span class="n"&gt;x&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="n"&gt;dx&lt;/span&gt;
        &lt;span class="kr"&gt;end&lt;/span&gt;
    &lt;span class="kr"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;p&gt;All that&amp;#8217;s left to do is the drawing itself.  For each tile in the &lt;code&gt;pose&lt;/code&gt; list, I draw that tile.  Each row is two tiles wide, so after every second tile, I reset the horizontal &amp;#8220;cursor&amp;#8221; (&lt;code&gt;x&lt;/code&gt;) back to where it started and move down by one row&amp;#8217;s worth of pixels.  For any other tile, I just move horizontally by &lt;code&gt;dx&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;The results are basically&amp;nbsp;magic.&lt;/p&gt;
&lt;div class="prose-full-illustration"&gt;
&lt;img src="https://eev.ee/media/gamedev-from-scratch/v1-anise-moving3.gif" alt="Star Anise walking around the screen and turning to face the way he's moving"&gt;
&lt;/div&gt;

&lt;p&gt;And that&amp;#8217;s a good place to pause for now.  Yes, I know, we didn&amp;#8217;t get very far, but this &lt;em&gt;is&lt;/em&gt; part zero!  It&amp;#8217;s mostly a test of this series and its tone for me, and a test of fortitude for you.  I hope you could follow along with the minor mathematical hijinks above, because next time it gets &lt;em&gt;much&lt;/em&gt; worse — before I can do anything else at all, I have to write &lt;em&gt;collision detection&lt;/em&gt;.  Oh boy!  Stay tuned!  And always feel free to ask questions, of me or anyone&amp;nbsp;else!&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;&lt;a href="https://eev.ee/blog/2021/01/26/gamedev-from-scratch-1-scaffolding/"&gt;Part 1: Scaffolding&amp;nbsp;→&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;
&lt;h2 id="appendix-pico-8-lua-extensions"&gt;&lt;a class="toclink" href="#appendix-pico-8-lua-extensions"&gt;Appendix: PICO-8 Lua&amp;nbsp;extensions&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Here are all the modifications &lt;span class="caps"&gt;PICO&lt;/span&gt;-8 has made to the language (based on Lua 5.2).  If you&amp;#8217;ve never used Lua, keep in mind that these won&amp;#8217;t carry over if you try to write Lua anywhere else.  Some of these are advanced features, so if you have no idea what something means, that&amp;#8217;s probably&amp;nbsp;fine.&lt;/p&gt;
&lt;p&gt;Spoilers: it&amp;#8217;s mostly that the standard library has&amp;nbsp;changed.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Numbers are signed 15.16 fixed-point, rather than stock Lua&amp;#8217;s 64-bit floating point.  That means fractions can only be represented in increments of 0.0000152587890625 (= &lt;span class="math"&gt;\(2^{-16}\)&lt;/span&gt;, a cumbersome number I refer to as the &amp;#8220;Planck size&amp;#8221;), and numbers can&amp;#8217;t exceed&amp;nbsp;±32768.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Compound assignment is supported: &lt;code&gt;a += b&lt;/code&gt; works as in &lt;code&gt;a = a + b&lt;/code&gt; in stock Lua, where &lt;code&gt;+&lt;/code&gt; can be replaced with any binary&amp;nbsp;operator.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;!=&lt;/code&gt; is allowed as an alias for &lt;code&gt;~=&lt;/code&gt;.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;if (foo) bar = 1&lt;/code&gt; is shorthand for &lt;code&gt;if foo then bar = 1 end&lt;/code&gt;.  The parentheses are required, and the condition ends at the end of the line.  (I strongly advise against using this unless you&amp;#8217;re very desperate for space; it scans poorly and doesn&amp;#8217;t even save&amp;nbsp;tokens.)&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;The new &lt;code&gt;@&lt;/code&gt;, &lt;code&gt;%&lt;/code&gt;, and &lt;code&gt;$&lt;/code&gt; unary prefix operators read 1, 2, or 4 bytes from a memory address.  (&lt;span class="caps"&gt;PICO&lt;/span&gt;-8&amp;#8217;s memory, not system &lt;span class="caps"&gt;RAM&lt;/span&gt;!)&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;The &lt;code&gt;?&lt;/code&gt; unary prefix operator is equivalent to &lt;code&gt;print&lt;/code&gt;.  (I&amp;#8217;ve never used it, and it&amp;#8217;s not even directly&amp;nbsp;documented.)&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;The built-in functions &lt;code&gt;collectgarbage&lt;/code&gt;, &lt;code&gt;dofile&lt;/code&gt;, &lt;code&gt;error&lt;/code&gt;, &lt;code&gt;pcall&lt;/code&gt;, &lt;code&gt;require&lt;/code&gt;, &lt;code&gt;select&lt;/code&gt;, and &lt;code&gt;xpcall&lt;/code&gt; are not available (though the lack of &lt;code&gt;select&lt;/code&gt; might be a&amp;nbsp;bug).&lt;/p&gt;
&lt;p&gt;The built-in variables &lt;code&gt;_G&lt;/code&gt; and &lt;code&gt;_VERSION&lt;/code&gt; are not&amp;nbsp;available.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;load&lt;/code&gt; has been replaced with a function that loads &lt;span class="caps"&gt;PICO&lt;/span&gt;-8 carts from&amp;nbsp;files.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;print&lt;/code&gt; has been replaced with a drawing function, which prints a single string at a position on&amp;nbsp;screen.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;tonumber&lt;/code&gt; and &lt;code&gt;tostring&lt;/code&gt; have been replaced with &lt;code&gt;tonum&lt;/code&gt; and &lt;code&gt;tostr&lt;/code&gt;, which behave slightly differently (but &lt;code&gt;tostr&lt;/code&gt; does still respect the &lt;code&gt;__tostring&lt;/code&gt; metatable&amp;nbsp;field).&lt;/p&gt;
&lt;p&gt;(&lt;code&gt;assert&lt;/code&gt;, &lt;code&gt;getmetatable&lt;/code&gt;, &lt;code&gt;ipairs&lt;/code&gt;, &lt;code&gt;next&lt;/code&gt;, &lt;code&gt;pairs&lt;/code&gt;, &lt;code&gt;rawequal&lt;/code&gt;, &lt;code&gt;rawget&lt;/code&gt;, &lt;code&gt;rawlen&lt;/code&gt;, &lt;code&gt;rawset&lt;/code&gt;, &lt;code&gt;setmetatable&lt;/code&gt;, and &lt;code&gt;type&lt;/code&gt; still exist and work as in stock&amp;nbsp;Lua.)&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;The &lt;code&gt;coroutine&lt;/code&gt; library is not available, but most of its contents are exposed directly as &lt;code&gt;cocreate&lt;/code&gt;, &lt;code&gt;coresume&lt;/code&gt;, &lt;code&gt;costatus&lt;/code&gt;, and &lt;code&gt;yield&lt;/code&gt;.  There is no equivalent for &lt;code&gt;coroutine.running&lt;/code&gt; or &lt;code&gt;coroutine.wrap&lt;/code&gt;.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;The &lt;code&gt;require&lt;/code&gt; function and &lt;code&gt;package&lt;/code&gt; library are not available, though the &lt;code&gt;#include&lt;/code&gt; syntax can be used to textually substitute the contents of a Lua&amp;nbsp;file.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;The &lt;code&gt;string&lt;/code&gt; library is not available.  Replacement string functions are: &lt;code&gt;chr&lt;/code&gt;, &lt;code&gt;ord&lt;/code&gt;, &lt;code&gt;split&lt;/code&gt;, and &lt;code&gt;sub&lt;/code&gt;.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;The &lt;code&gt;table&lt;/code&gt; library is not available.  Replacement table functions are: &lt;code&gt;add&lt;/code&gt;, &lt;code&gt;del&lt;/code&gt;, &lt;code&gt;deli&lt;/code&gt;, &lt;code&gt;count&lt;/code&gt;, &lt;code&gt;all&lt;/code&gt;, &lt;code&gt;foreach&lt;/code&gt;.  There is no built-in way to concatenate or sort a&amp;nbsp;list.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;The &lt;code&gt;math&lt;/code&gt; library is not available.  Replacement math functions are: &lt;code&gt;max&lt;/code&gt;, &lt;code&gt;min&lt;/code&gt;, &lt;code&gt;mid&lt;/code&gt;, &lt;code&gt;flr&lt;/code&gt;, &lt;code&gt;ceil&lt;/code&gt;, &lt;code&gt;sin&lt;/code&gt;, &lt;code&gt;cos&lt;/code&gt;, &lt;code&gt;atan2&lt;/code&gt;, &lt;code&gt;sqrt&lt;/code&gt;, &lt;code&gt;abs&lt;/code&gt;, &lt;code&gt;rnd&lt;/code&gt;, &lt;code&gt;srand&lt;/code&gt;.  There is also an integer division operator, &lt;code&gt;\&lt;/code&gt;.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;The &lt;code&gt;bit32&lt;/code&gt; library is not available, but bitwise operations are available as both functions — &lt;code&gt;band&lt;/code&gt;, &lt;code&gt;bor&lt;/code&gt;, &lt;code&gt;bxor&lt;/code&gt;, &lt;code&gt;bnot&lt;/code&gt;, &lt;code&gt;shl&lt;/code&gt;, &lt;code&gt;shr&lt;/code&gt;, &lt;code&gt;lshr&lt;/code&gt;, &lt;code&gt;rotl&lt;/code&gt;, &lt;code&gt;rotr&lt;/code&gt; — and operators — &lt;code&gt;&amp;amp;&lt;/code&gt;, &lt;code&gt;|&lt;/code&gt;, &lt;code&gt;^^&lt;/code&gt;, &lt;code&gt;~&lt;/code&gt;, &lt;code&gt;&amp;lt;&amp;lt;&lt;/code&gt;, &lt;code&gt;&amp;gt;&amp;gt;&lt;/code&gt;, &lt;code&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/code&gt;, &lt;code&gt;&amp;lt;&amp;lt;&amp;gt;&lt;/code&gt;, &lt;code&gt;&amp;gt;&amp;gt;&amp;lt;&lt;/code&gt;.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;The &lt;code&gt;io&lt;/code&gt; library is not available.  Running &lt;span class="caps"&gt;PICO&lt;/span&gt;-8 cartridges have no notion of a&amp;nbsp;filesystem.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;The &lt;code&gt;os&lt;/code&gt; library is not available.  Running &lt;span class="caps"&gt;PICO&lt;/span&gt;-8 cartridges have no direct access to the underlying operating system.  (Some facilities are exposed through the &amp;#8220;syscall&amp;#8221; function &lt;code&gt;stat&lt;/code&gt;, such as accessing the current &lt;span class="caps"&gt;UTC&lt;/span&gt; or local&amp;nbsp;time.)&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;The &lt;code&gt;debug&lt;/code&gt; library is not&amp;nbsp;available.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;A number of other new functions were added, though I won&amp;#8217;t list them all here; they&amp;#8217;re generally for drawing, working with assets, or interacting with the &lt;span class="caps"&gt;PICO&lt;/span&gt;-8&amp;#8217;s faux&amp;nbsp;hardware.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;script type="text/javascript"&gt;if (!document.getElementById('mathjaxscript_pelican_#%@#$@#')) {
    var align = "center",
        indent = "0em",
        linebreak = "false";

    if (false) {
        align = (screen.width &lt; 768) ? "left" : align;
        indent = (screen.width &lt; 768) ? "0em" : indent;
        linebreak = (screen.width &lt; 768) ? 'true' : linebreak;
    }

    var mathjaxscript = document.createElement('script');
    mathjaxscript.id = 'mathjaxscript_pelican_#%@#$@#';
    mathjaxscript.type = 'text/javascript';
    mathjaxscript.src = 'https://cdnjs.cloudflare.com/ajax/libs/mathjax/2.7.3/latest.js?config=TeX-AMS-MML_HTMLorMML';

    var configscript = document.createElement('script');
    configscript.type = 'text/x-mathjax-config';
    configscript[(window.opera ? "innerHTML" : "text")] =
        "MathJax.Hub.Config({" +
        "    config: ['MMLorHTML.js']," +
        "    TeX: { extensions: ['AMSmath.js','AMSsymbols.js','noErrors.js','noUndefined.js'], equationNumbers: { autoNumber: 'none' } }," +
        "    jax: ['input/TeX','input/MathML','output/HTML-CSS']," +
        "    extensions: ['tex2jax.js','mml2jax.js','MathMenu.js','MathZoom.js']," +
        "    displayAlign: '"+ align +"'," +
        "    displayIndent: '"+ indent +"'," +
        "    showMathMenu: true," +
        "    messageStyle: 'normal'," +
        "    tex2jax: { " +
        "        inlineMath: [ ['\\\\(','\\\\)'] ], " +
        "        displayMath: [ ['$$','$$'] ]," +
        "        processEscapes: true," +
        "        preview: 'TeX'," +
        "    }, " +
        "    'HTML-CSS': { " +
        "        availableFonts: ['STIX', 'TeX']," +
        "        preferredFont: 'STIX'," +
        "        styles: { '.MathJax_Display, .MathJax .mo, .MathJax .mi, .MathJax .mn': {color: 'inherit ! important'} }," +
        "        linebreaks: { automatic: "+ linebreak +", width: '90% container' }," +
        "    }, " +
        "}); " +
        "if ('default' !== 'default') {" +
            "MathJax.Hub.Register.StartupHook('HTML-CSS Jax Ready',function () {" +
                "var VARIANT = MathJax.OutputJax['HTML-CSS'].FONTDATA.VARIANT;" +
                "VARIANT['normal'].fonts.unshift('MathJax_default');" +
                "VARIANT['bold'].fonts.unshift('MathJax_default-bold');" +
                "VARIANT['italic'].fonts.unshift('MathJax_default-italic');" +
                "VARIANT['-tex-mathit'].fonts.unshift('MathJax_default-italic');" +
            "});" +
            "MathJax.Hub.Register.StartupHook('SVG Jax Ready',function () {" +
                "var VARIANT = MathJax.OutputJax.SVG.FONTDATA.VARIANT;" +
                "VARIANT['normal'].fonts.unshift('MathJax_default');" +
                "VARIANT['bold'].fonts.unshift('MathJax_default-bold');" +
                "VARIANT['italic'].fonts.unshift('MathJax_default-italic');" +
                "VARIANT['-tex-mathit'].fonts.unshift('MathJax_default-italic');" +
            "});" +
        "}";

    (document.body || document.getElementsByTagName('head')[0]).appendChild(configscript);
    (document.body || document.getElementsByTagName('head')[0]).appendChild(mathjaxscript);
}
&lt;/script&gt;</content><category term="blog"></category><category term="tech"></category><category term="gamedev"></category></entry><entry><title>Rowling is dangerously wrong</title><link href="https://eev.ee/blog/2020/06/11/rowling-is-dangerously-wrong/" rel="alternate"></link><published>2020-06-11T11:15:00-07:00</published><updated>2020-06-11T11:15:00-07:00</updated><author><name>Eevee</name></author><id>tag:eev.ee,2020-06-11:/blog/2020/06/11/rowling-is-dangerously-wrong/</id><summary type="html">&lt;p&gt;I read &lt;span class="caps"&gt;J.K.&lt;/span&gt; Rowling’s &lt;a href="https://www.jkrowling.com/opinions/j-k-rowling-writes-about-her-reasons-for-speaking-out-on-sex-and-gender-issues/"&gt;essay&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;I regret doing so.&lt;/p&gt;
&lt;p&gt;Here are some thoughts.  Trans readers, brace yourselves, especially if you didn’t read the original.&lt;/p&gt;
&lt;p&gt;Some help came from &lt;a href="https://twitter.com/Carter_AndrewJ/status/1270787941275762689"&gt;Andrew James Carter’s response thread&lt;/a&gt;, which has many more citations but feels less compelling to a general audience to me.&lt;/p&gt;
</summary><content type="html">&lt;p&gt;I read &lt;span class="caps"&gt;J.K.&lt;/span&gt; Rowling&amp;#8217;s &lt;a href="https://www.jkrowling.com/opinions/j-k-rowling-writes-about-her-reasons-for-speaking-out-on-sex-and-gender-issues/"&gt;essay&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;I regret doing&amp;nbsp;so.&lt;/p&gt;
&lt;p&gt;Here are some thoughts.  Trans readers, brace yourselves, especially if you didn&amp;#8217;t read the&amp;nbsp;original.&lt;/p&gt;
&lt;p&gt;Some help came from &lt;a href="https://twitter.com/Carter_AndrewJ/status/1270787941275762689"&gt;Andrew James Carter&amp;#8217;s response thread&lt;/a&gt;, which has many more citations but feels less compelling to a general audience to&amp;nbsp;me.&lt;/p&gt;


&lt;hr /&gt;
&lt;blockquote&gt;
&lt;p&gt;This isn’t an easy piece to write, for reasons that will shortly become clear, but I know it’s time to explain myself on an issue surrounded by toxicity. I write this without any desire to add to that&amp;nbsp;toxicity.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;I admire that.  I, too, would prefer not to add to the&amp;nbsp;toxicity.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;For people who don’t know: last December I tweeted my support for Maya Forstater, a tax specialist who’d lost her job for what were deemed ‘transphobic’ tweets. She took her case to an employment tribunal, asking the judge to rule on whether a philosophical belief that sex is determined by biology is protected in law. Judge Tayler ruled that it&amp;nbsp;wasn’t.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;We are off to a poor start.  Framing an unrenewed contract as &amp;#8220;losing her job&amp;#8221; is dubious.  And specifically, Judge Tayler &lt;a href="https://drive.google.com/file/d/12P9zf82TicPs2cCxlTnm0TrNFDD8Gaz5/view"&gt;ruled&lt;/a&gt; that &amp;#8220;she will refer to a person by the sex she considered appropriate even if it violates their dignity and/or creates an intimidating, hostile, degrading, humiliating or offensive environment&amp;#8221; — that is, she would be actively and knowingly rude towards people in the workplace, and &lt;em&gt;that&lt;/em&gt; is not&amp;nbsp;protected.&lt;/p&gt;
&lt;p&gt;(Forstater later disingenuously claimed to have lost her job for &amp;#8220;speaking up about women&amp;#8217;s rights&amp;#8221;.  And I&amp;#8217;m just now learning that she compared the use of correct pronouns to the use of rohypnol — the date rape drug — &lt;em&gt;while&lt;/em&gt; this court case was pending.  &lt;a href="https://www.nbcnews.com/think/opinion/j-k-rowling-s-maya-forstater-tweets-support-hostile-work-ncna1105201"&gt;Charming.&lt;/a&gt;)&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;All the time I’ve been researching and learning, accusations and threats from trans activists have been bubbling in my Twitter timeline. This was initially triggered by a ‘like’. When I started taking an interest in gender identity and transgender matters, I began screenshotting comments that interested me, as a way of reminding myself what I might want to research later. On one occasion, I absent-mindedly ‘liked’ instead of screenshotting. That single ‘like’ was deemed evidence of wrongthink, and a persistent low level of harassment&amp;nbsp;began.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;This sounds like a simple misunderstanding which could have been resolved with a single explanatory tweet.  Instead, your spokesperson &lt;a href="https://www.pinknews.co.uk/2018/03/22/jk-rowling-reps-blame-middle-aged-moment-for-liking-tweet-calling-trans-women-men-in-dresses/"&gt;referred&lt;/a&gt; to it as a &amp;#8220;clumsy and middle-aged moment&amp;#8221;.  And now you categorize the tweet vaguely as something to research — suggesting to a casual reader that you had merely liked a link to a scholarly article, perhaps — when it was a mundane personal rant which referred to trans women as &amp;#8220;men in&amp;nbsp;dresses&amp;#8221;.&lt;/p&gt;
&lt;p&gt;I have a hypothesis about where the toxicity began — right there, when you clicked the heart underneath it.  It&amp;#8217;s something you know is mean and hurtful to the people it describes, and is &lt;em&gt;intended&lt;/em&gt; to be so, and you not only defend it but cloak it in an obligatory 1984 reference.  This is deceptive, mean-spirited, and&amp;nbsp;shameful.&lt;/p&gt;
&lt;p&gt;We are only on paragraph&amp;nbsp;four.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Months later, I compounded my accidental ‘like’ crime by following Magdalen Burns on Twitter. Magdalen was an immensely brave young feminist and lesbian who was dying of an aggressive brain tumour. I followed her because I wanted to contact her directly, which I succeeded in doing. However, as Magdalen was a great believer in the importance of biological sex, and didn’t believe lesbians should be called bigots for not dating trans women with penises, dots were joined in the heads of twitter trans activists, and the level of social media abuse&amp;nbsp;increased.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;span class="dquo"&gt;&amp;#8220;&lt;/span&gt;You are fucking blackface actors. You aren&amp;#8217;t women. You&amp;#8217;re men who get sexual kicks from being treated like women. fuck you and your dirty fucking perversions. our oppression isn&amp;#8217;t a fetish you pathetic, sick,&amp;nbsp;fuck.&amp;#8221;&lt;/p&gt;
&lt;p&gt;That&amp;#8217;s what Magdalen Berns, whose name you misspelled, had to say about trans women.  (Ironically, it&amp;#8217;s not too far off from what folks used to say — and occasionally still do — about gay folks.)  I&amp;#8217;m going to hazard a guess that this was more of a concern than any discourse about who lesbians choose to&amp;nbsp;date.&lt;/p&gt;
&lt;p&gt;The funny thing is, while I&amp;#8217;ve seen the &amp;#8220;gender critical&amp;#8221; crowd complain &lt;em&gt;numerous&lt;/em&gt; times that trans women are somehow trying to force cis lesbians to have sex with them (by tweeting about it?), I&amp;#8217;ve virtually never witnessed the phenomenon directly — and I am &lt;em&gt;neck-deep&lt;/em&gt; in trans Twitter.  Perhaps two or three times over the years, I&amp;#8217;ve seen some discourse about &amp;#8220;genital attraction&amp;#8221; and whether it&amp;#8217;s socially influenced, which I suppose is an interesting question.  On one singular occasion, such a tweet came uncomfortably close to suggesting that people were obligated to correct for what&amp;#8217;s presumed to be social influence in who they&amp;#8217;re attracted to, and I swiftly pushed back against&amp;nbsp;it.&lt;/p&gt;
&lt;p&gt;But the way &amp;#8220;gender critical&amp;#8221; folks talk about this, you&amp;#8217;d think it was the only topic trans women ever discuss!  Meanwhile, do you know who most trans women I know are dating?  &lt;em&gt;Each&amp;nbsp;other!&lt;/em&gt;&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;I mention all this only to explain that I knew perfectly well what was going to happen when I supported Maya. I must have been on my fourth or fifth cancellation by&amp;nbsp;then.&lt;/p&gt;
&lt;p&gt;I expected the threats of violence, to be told I was literally killing trans people with my hate, to be called cunt and bitch and, of course, for my books to be burned, although one particularly abusive man told me he’d composted&amp;nbsp;them.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;I am genuinely sorry that people are abusive on Twitter, but I don&amp;#8217;t know how to avoid it when you have more followers than the populations of &lt;span class="caps"&gt;NYC&lt;/span&gt; and &lt;span class="caps"&gt;LA&lt;/span&gt; combined.  It&amp;#8217;s a much broader problem, though definitely exacerbated when you support someone who has been fighting for the right to be deliberately&amp;nbsp;hostile.&lt;/p&gt;
&lt;p&gt;I&amp;#8217;m not sure what to make of the last part.  Is composting a book worse than burning it?  And are you hinting a comparison between burning one&amp;#8217;s own personal property and the actions of Nazi Germany, or am I reading too much into this conspicuous phrasing?  I hope the latter, because the former would be &lt;em&gt;extremely&lt;/em&gt; tasteless, considering that part of what was burned was the research and library of &lt;a href="https://en.wikipedia.org/wiki/Institut_f%C3%BCr_Sexualwissenschaft"&gt;a sex research institute&lt;/a&gt; which was &lt;em&gt;founded by&lt;/em&gt; the man who coined the term &amp;#8220;transsexualism&amp;#8221; and had trans people as both staff and&amp;nbsp;clients.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;What I didn’t expect in the aftermath of my cancellation was the avalanche of emails and letters that came showering down upon me, the overwhelming majority of which were positive, grateful and supportive. They came from a cross-section of kind, empathetic and intelligent people, some of them working in fields dealing with gender dysphoria and trans people, who’re all deeply concerned about the way a socio-political concept is influencing politics, medical practice and safeguarding. They’re worried about the dangers to young people, gay people and about the erosion of women’s and girl’s [sic] rights. Above all, they’re worried about a climate of fear that serves nobody – least of all trans youth –&amp;nbsp;well.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;I note, conspicuously, that zero of them were &lt;em&gt;from trans people&lt;/em&gt;, or you surely would&amp;#8217;ve mentioned as much.  You give trans youth a token mention at the end, but only as an object of external concern, not as people to be listened to and trusted about their own experiences.  This is a theme that I see we&amp;#8217;ll be&amp;nbsp;revisiting.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;I’d stepped back from Twitter for many months both before and after tweeting support for Maya, because I knew it was doing nothing good for my mental health. I only returned because I wanted to share a free children’s book during the pandemic. Immediately, activists who clearly believe themselves to be good, kind and progressive people swarmed back into my timeline, assuming a right to police my speech, accuse me of hatred, call me misogynistic slurs and, above all – as every woman involved in this debate will know – &lt;span class="caps"&gt;TERF&lt;/span&gt;.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;I note for the audience that the &amp;#8220;gender critical&amp;#8221; crowd — you know, TERFs — love to use the term &lt;span class="caps"&gt;TRA&lt;/span&gt; (trans rights activist) to refer to pretty much any trans person who doesn&amp;#8217;t buy what they&amp;#8217;re selling.  I don&amp;#8217;t know if this is meant to be a dogwhistle, but it at least quacks like&amp;nbsp;one.&lt;/p&gt;
&lt;p&gt;More generally, &amp;#8220;activists&amp;#8221; is a favored scare word across the political spectrum, much like &amp;#8220;ideology&amp;#8221; — it conjures the image of someone who is angrily trying to push Politics on you, while neatly obscuring that the political view they&amp;#8217;re trying to push is &amp;#8220;please don&amp;#8217;t be cruel to me or people like me&amp;#8221;.  Are you, Rowling, not an activist?   What about the people you support, like Berns?  You use &amp;#8220;activist&amp;#8221; ten times in this essay, and every single time to describe trans&amp;nbsp;people.&lt;/p&gt;
&lt;p&gt;It&amp;#8217;s rhetorical sleight of hand.  Trans people who want to live their lives without being called blackface actors are &amp;#8220;activists&amp;#8221;, while the people making those comments are merely expressing concerns.  Telling people what they should be able to &lt;em&gt;wear&lt;/em&gt; earns no mention in this essay at all, but replying on a public platform to tell you that you are being hurtful is &amp;#8220;policing your&amp;nbsp;speech&amp;#8221;.&lt;/p&gt;
&lt;p&gt;Do you know where I first learned about this trick?  From people who opposed the gay rights movement.  &amp;#8220;Gay rights activist&amp;#8221; was a phrase I saw bandied about a &lt;em&gt;lot&lt;/em&gt; while I was growing up, as though wanting to be able to marry one&amp;#8217;s partner instantly transformed a person into some sort of unreasonable lobbyist, while opposing it was just the normal and natural thing to do.  Frequently they&amp;#8217;d have one gay person who agreed with them to put on a pedestal, the proof that they didn&amp;#8217;t actually hate gay people — at least not the ones who&amp;#8217;d sit down and shut up and accept whatever scraps they were&amp;nbsp;given.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;If you didn’t already know – and why should you? – ‘&lt;span class="caps"&gt;TERF&lt;/span&gt;’ is an acronym coined by trans activists, which stands for Trans-Exclusionary Radical Feminist. In practice, a huge and diverse cross-section of women are currently being called TERFs and the vast majority have never been radical feminists. Examples of so-called TERFs range from the mother of a gay child who was afraid their child wanted to transition to escape homophobic bullying, to a hitherto totally unfeminist older lady who’s vowed never to visit Marks &lt;span class="amp"&gt;&amp;amp;&lt;/span&gt; Spencer again because they’re allowing any man who says they identify as a woman into the women’s changing&amp;nbsp;rooms.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;As any best-selling author would know, if a word is used incorrectly at least two times on Twitter, it loses all&amp;nbsp;meaning.&lt;/p&gt;
&lt;p&gt;From what I&amp;#8217;ve observed, the vast majority of people referred to as TERFs are people who claim an interest in the well-being of women and lesbians, but exclude trans women from that (or outright classify them all as predators), treat trans men as confused women, speak over or outright ignore the people they claim to be defending, and spend an awful lot of time inventing or vastly exacerbating &amp;#8220;concerns&amp;#8221; about trans people so as to excuse spending an awful lot of the rest of their time saying incredibly nasty&amp;nbsp;things.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Ironically, radical feminists aren’t even trans-exclusionary – they include trans men in their feminism, because they were born&amp;nbsp;women.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;This &lt;em&gt;is&lt;/em&gt; trans-exclusionary.  It&amp;#8217;s feminism that ignores and talks over trans men, which is a strange thing for feminists to do to people they consider to be&amp;nbsp;women.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;But accusations of TERFery have been sufficient to intimidate many people, institutions and organisations I once admired, who’re cowering before the tactics of the playground. ‘They’ll call us transphobic!’ ‘They’ll say I hate trans people!’ What next, they’ll say you’ve got&amp;nbsp;fleas?&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Not wanting to come across as hating a group of people is generally considered polite.  Imagine saying this about, I don&amp;#8217;t know,&amp;nbsp;lesbians.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Speaking as a biological woman, a lot of people in positions of power really need to grow a pair (which is doubtless literally possible, according to the kind of people who argue that clownfish prove humans aren’t a dimorphic&amp;nbsp;species).&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Is &amp;#8220;courage is stored in the balls&amp;#8221; feminist&amp;nbsp;now?&lt;/p&gt;
&lt;p&gt;But since you bring up dimorphism, here&amp;#8217;s a fun anecdote that&amp;#8217;s relevant to my field.  It seems that one of the biggest factors a neural network (&amp;#8220;&lt;span class="caps"&gt;AI&lt;/span&gt;&amp;#8221;) uses to determine a person&amp;#8217;s gender is&amp;#8230;  &lt;a href="https://medium.com/@kerryrodden/is-that-a-boy-or-a-girl-cb93abbae6da"&gt;hair length&lt;/a&gt;!  Which isn&amp;#8217;t a dimorphic trait, at least not how you&amp;#8217;d think.  The sexes are not really all that distinct; much of it is decoration we put on ourselves to exacerbate the differences, for &lt;a href="https://twitter.com/dorrismccomics/status/1270032808434696193"&gt;some reason&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;For some more anecdotes, feel free to look for reports of cis lesbians being kicked out of public women&amp;#8217;s restrooms for looking too masculine.  Like &lt;a href="https://www.foxnews.com/wires/2008May13/0,4670,GayCustomerLawsuit,00.html"&gt;this one&lt;/a&gt;, or &lt;a href="https://www.gaystarnews.com/article/lesbian-kicked-out-of-bowling-alley-because-she-used-the-womens-restroom/"&gt;this one&lt;/a&gt;, or &lt;a href="https://www.towleroad.com/2016/04/police-force-lesbian-to-leave-bathroom-for-failing-to-show-id-prove-shes-a-woman-watch/"&gt;this one&lt;/a&gt;, or &lt;a href="https://www.mirror.co.uk/news/uk-news/lesbian-couple-kicked-out-womens-4977298"&gt;this one&lt;/a&gt;.  Whose activism do you suppose would exacerbate&amp;nbsp;this?&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Firstly, I have a charitable trust that focuses on alleviating social deprivation in Scotland, with a particular emphasis on women and children. Among other things, my trust supports projects for female prisoners and for survivors of domestic and sexual abuse. I also fund medical research into &lt;span class="caps"&gt;MS&lt;/span&gt;, a disease that behaves very differently in men and women. It’s been clear to me for a while that the new trans activism is having (or is likely to have, if all its demands are met) a significant impact on many of the causes I support, because it’s pushing to erode the legal definition of sex and replace it with&amp;nbsp;gender.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;What a perfect example.  What does it &lt;em&gt;mean&lt;/em&gt; for &lt;span class="caps"&gt;MS&lt;/span&gt; to behave very differently in men and women?  &amp;#8220;Man&amp;#8221; versus &amp;#8220;woman&amp;#8221; isn&amp;#8217;t a switch you flip; it&amp;#8217;s a combination of dozens of factors.  If the difference is caused by hormone levels — which &lt;a href="https://www.hopkinsmedicine.org/health/conditions-and-diseases/multiple-sclerosis-ms/multiple-sclerosis-why-are-women-more-at-risk"&gt;looks plausible&lt;/a&gt; — then trans women on &lt;span class="caps"&gt;HRT&lt;/span&gt; will be affected similarly to cis women, because they have the same levels of estrogen!  And by excluding them — by insisting we talk only about &amp;#8220;biological&amp;#8221; &amp;#8220;men&amp;#8221; and &amp;#8220;women&amp;#8221; rather than specific biological factors — you are &lt;em&gt;miscategorizing&lt;/em&gt; them for no&amp;nbsp;reason.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;The second reason is that I’m an ex-teacher and the founder of a children’s charity, which gives me an interest in both education and safeguarding. Like many others, I have deep concerns about the effect the trans rights movement is having on&amp;nbsp;both.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Ah, you mean Lumos, the charity you cofounded with Baroness Emma Nicholson, who &lt;em&gt;just yesterday&lt;/em&gt; &lt;a href="https://www.pinknews.co.uk/2020/06/10/baroness-emma-nicholson-same-sex-marriage-equality-tweets-twitter-homophobia/"&gt;said that gay marriage is degrading women&amp;#8217;s rights&lt;/a&gt; after attempting to repeal it in 2013?  &lt;em&gt;I&lt;/em&gt; have some deep concerns about the effect this person will have on the well-being of gay teens — and she&amp;#8217;s not a mere &amp;#8220;activist&amp;#8221; or &amp;#8220;movement&amp;#8221;, but a lawmaker!  Strange company you keep.  And that&amp;#8217;s not even getting into how &lt;a href="https://twitter.com/KatyMontgomerie/status/1267911095332876289"&gt;she called it pedophilia&lt;/a&gt; for a trans charity&amp;#8217;s website to have an escape button on it in case of abusive parents, a mere week and a half&amp;nbsp;ago.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;The third is that, as a much-banned author, I’m interested in freedom of speech and have publicly defended it, even unto Donald&amp;nbsp;Trump.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;span class="dquo"&gt;&amp;#8220;&lt;/span&gt;Much-banned&amp;#8221;?  You wrote one of the best-selling books of all time and &lt;em&gt;the&lt;/em&gt; best-selling series of all time.  You have sold at least one book for every fourteen humans alive and made almost a dozen movie deals.  When you tweet, it trends for days and makes national headlines.  Your freedom of speech is not at risk here — and if it were, you could probably afford to inscribe whatever you wanted to say on the face of the&amp;nbsp;moon.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;The fourth is where things start to get truly personal. I’m concerned about the huge explosion in young women [sic] wishing to transition and also about the increasing numbers who seem to be detransitioning (returning to their original sex), because they regret taking steps that have, in some cases, altered their bodies irrevocably, and taken away their fertility. Some say they decided to transition after realising they were same-sex attracted, and that transitioning was partly driven by homophobia, either in society or in their&amp;nbsp;families.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Yes, it&amp;#8217;s truly tragic that homophobia is still rampant, such as in the baroness you cofounded a charity with.  Especially in parents.  Incidentally, the most common reason given for detransitioning — which is &lt;a href="https://transequality.org/sites/default/files/docs/usts/USTS-Full-Report-Dec17.pdf"&gt;pressure from a parent&lt;/a&gt; (36%, see page 108); the next is harassment/discrimination (31%), followed by having trouble getting a job (29%).  Most of the other reasons given were pressure from some other external source.  Only 0.4% of the people in that survey reported detransitioning because they simply did not like transition.  And, by the way, detransition (even temporarily) is several times more common in trans women than trans&amp;nbsp;men.&lt;/p&gt;
&lt;p&gt;If you really want to fight detransition, the most effective action you could take would be to delete this post.  But you&amp;#8217;re approaching this from the perspective that trans men are confused, just like swaths of homophobic parents have said of their gay&amp;nbsp;children.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Most people probably aren’t aware – I certainly wasn’t, until I started researching this issue properly – that ten years ago, the majority of people wanting to transition to the opposite sex were male. That ratio has now reversed. The &lt;span class="caps"&gt;UK&lt;/span&gt; has experienced a 4400% increase in girls [sic] being referred for transitioning treatment. Autistic girls [sic] are hugely overrepresented in their&amp;nbsp;numbers.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Of course they are.  Trans people are &lt;a href="https://www.eurekalert.org/pub_releases/2019-07/aru-sft071619.php"&gt;disproportionately autistic&lt;/a&gt;, so this is to be expected.  I&amp;#8217;d think this would be cause for celebration — people are being treated who previously wouldn&amp;#8217;t have been!  That&amp;#8217;s excellent&amp;nbsp;progress.&lt;/p&gt;
&lt;p&gt;But instead of celebrating it, you suggest here that autistic trans &lt;strong&gt;boys&lt;/strong&gt; are being taken advantage of.  No, worse; you suggest that autistic trans boys are incapable of making decisions about their own lives, and don&amp;#8217;t even respect them enough to refer to them as they wish to be referred to.  You speak over them, dismiss them as obviously wrong out of hand, and ignore how they wish to be referred to while pretending to care about their well-being.  This is deeply condescending and&amp;nbsp;appalling.&lt;/p&gt;
&lt;p&gt;As an aside, it&amp;#8217;s quite frustrating that you so frequently refuse to connect the dots — instead you leave a trail of breadcrumbs and let some haunting conclusion form in the reader&amp;#8217;s head, while retaining plausible deniability for yourself because you never actually &lt;em&gt;said&lt;/em&gt; the things you&amp;#8217;re trying to imply.  That leaves you free to claim that a response like this one, which spells out the winks and nods, is yet more dismissable&amp;nbsp;harassment.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;The same phenomenon has been seen in the &lt;span class="caps"&gt;US&lt;/span&gt;. In 2018,  American physician and researcher Lisa Littman set out to explore it. In an interview, she&amp;nbsp;said:&lt;/p&gt;
&lt;p&gt;‘Parents online were describing a very unusual pattern of transgender-identification where multiple friends and even entire friend groups became transgender-identified at the same time. I would have been remiss had I not considered social contagion and peer influences as potential&amp;nbsp;factors.’&lt;/p&gt;
&lt;p&gt;Littman mentioned Tumblr, Reddit, Instagram and YouTube as contributing factors to Rapid Onset Gender Dysphoria, where she believes that in the realm of transgender identification ‘youth have created particularly insular echo&amp;nbsp;chambers.’&lt;/p&gt;
&lt;p&gt;Her paper caused a furore. She was accused of bias and of spreading misinformation about transgender people, subjected to a tsunami of abuse and a concerted campaign to discredit both her and her work. The journal took the paper offline and re-reviewed it before republishing&amp;nbsp;it.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;This is probably because &amp;#8220;rapid-onset gender dysphoria&amp;#8221; is &lt;a href="https://www.buzzfeednews.com/article/shannonkeating/rapid-onset-gender-dysphoria-flawed-methods-transgender"&gt;not a real phenomenon&lt;/a&gt;.  The critical flaw in the idea is so blatantly obvious that you&amp;#8217;ve very nearly spelled it out yourself: &lt;strong&gt;parents&lt;/strong&gt; described an &amp;#8220;unusual&amp;#8221; pattern of behavior.  Not the children themselves, not psychologists, not therapists.  Parents.  Parents who are upset that their children are coming out as trans, who are searching for some external factor to blame so they can rest assured that their children have simply been taken advantage of by some nefarious&amp;nbsp;force.&lt;/p&gt;
&lt;p&gt;I remember this all quite well from the 90s, except then it was about homosexuality.  (A pattern begins to emerge.)  There were no signs!, cry parents who punished their children for ever showing any signs, thus swiftly teaching them to put on a good act.  It must be the media.  It must be the evil other gays somehow influencing my poor child, who otherwise &lt;em&gt;would&lt;/em&gt; be straight, like I want them to&amp;nbsp;be.&lt;/p&gt;
&lt;p&gt;The only difference is that this time it&amp;#8217;s been given an acronym to lend it some veneer of credibility.  But it&amp;#8217;s not a clinical diagnosis; it&amp;#8217;s a study of the feelings of &lt;em&gt;parents&lt;/em&gt; who were caught off guard and are searching for an explanation other than &amp;#8220;my child is trans&amp;#8221;.  Even &lt;a href="https://journals.plos.org/plosone/article?id=10.1371/journal.pone.0214157"&gt;the paper itself&lt;/a&gt; has a preface saying the term &amp;#8220;should not be used in a way to imply that it explains the experiences of all gender dysphoric&amp;nbsp;youth&amp;#8221;.&lt;/p&gt;
&lt;p&gt;There&amp;#8217;s no mystery to be solved here, anyway.  Talk to a single queer person (who isn&amp;#8217;t isolated due to factors beyond their control) and I&amp;#8217;ll bet you they have disproportionately many queer friends.  People who are alike tend to clump together, especially if they sense that society at large is uncomfortable with them.  All that&amp;#8217;s been observed here is that trans teenagers form friend groups, and when one of them comes out, the others feel confident enough to come out as well.  And their parents don&amp;#8217;t like it, because of a culture that includes essays like this from household names with massive&amp;nbsp;platforms.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;However, her career took a similar hit to that suffered by Maya Forstater. Lisa Littman had dared challenge one of the central tenets of trans activism, which is that a person’s gender identity is innate, like sexual orientation. Nobody, the activists insisted, could ever be persuaded into being&amp;nbsp;trans.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;I remember this from the 90s, too.  I remember the argument having to be made that sexual orientation is fixed and absolute and predetermined — because, regardless of how true or universal that may or may not be, the alternative is to leave the door open for parents and communities to try to &amp;#8220;fix&amp;#8221; gay children and ostracize the gay adults who had &amp;#8220;persuaded&amp;#8221; them into being&amp;nbsp;gay.&lt;/p&gt;
&lt;p&gt;Here we go again, except the &amp;#8220;fix&amp;#8221; for trans youth is to merely tell them to knock it off because they&amp;#8217;re mistaken and leave it at&amp;nbsp;that.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;The argument of many current trans activists is that if you don’t let a gender dysphoric teenager transition, they will kill themselves. In an article explaining why he resigned from the Tavistock (an &lt;span class="caps"&gt;NHS&lt;/span&gt; gender clinic in England) psychiatrist Marcus Evans stated that claims that children will kill themselves if not permitted to transition do not ‘align substantially with any robust data or studies in this area. Nor do they align with the cases I have encountered over decades as a&amp;nbsp;psychotherapist.’&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;They won&amp;#8217;t &lt;em&gt;necessarily&lt;/em&gt; kill themselves, but you could throw a rock and hit a study telling you that trans folks have a shockingly high rate of suicide attempts, and the absolute number one factor that drops that rate precipitously is transition.  Or you could talk to a trans person and see if they have a friend who attempted/committed suicide because they were unable to transition (yes).  Or at the very least, maybe cite someone who &lt;em&gt;didn&amp;#8217;t&lt;/em&gt;&amp;nbsp;resign.&lt;/p&gt;
&lt;p&gt;What a shockingly insensitive thing to&amp;nbsp;say.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;The writings of young trans men reveal a group of notably sensitive and clever people.  The more of their accounts of gender dysphoria I’ve read, with their insightful descriptions of anxiety, dissociation, eating disorders, self-harm and self-hatred, the more I’ve wondered whether, if I’d been born 30 years later, I too might have tried to transition. The allure of escaping womanhood would have been huge. I struggled with severe &lt;span class="caps"&gt;OCD&lt;/span&gt; as a teenager. If I’d found community and sympathy online that I couldn’t find in my immediate environment, I believe I could have been persuaded to turn myself into the son my father had openly said he’d have&amp;nbsp;preferred.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;You call them clever, but immediately turn around and suggest that they are somehow artificially trans, that they have been &amp;#8220;persuaded&amp;#8221; into it.  Again, you express ostensible care but use it as a springboard to dismiss them and talk over them.  And what of trans women, who are well aware of what womanhood entails but still prefer it?  This is precisely what I mentioned as the common &lt;span class="caps"&gt;TERF&lt;/span&gt; rhetoric, and is why people are calling you one: you speak piteously of trans men while suggesting with every word that you know better than they do what&amp;#8217;s good for them, while trans women are&amp;#8230;  well, who knows what that omission might&amp;nbsp;imply?&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;When I read about the theory of gender identity, I remember how mentally sexless I felt in youth. I remember Colette’s description of herself as a ‘mental hermaphrodite’ and Simone de Beauvoir’s words: ‘It is perfectly natural for the future woman to feel indignant at the limitations posed upon her by her sex. The real question is not why she should reject them: the problem is rather to understand why she accepts&amp;nbsp;them.’&lt;/p&gt;
&lt;p&gt;As I didn’t have a realistic possibility of becoming a man back in the 1980s, it had to be books and music that got me through both my mental health issues and the sexualised scrutiny and judgement that sets so many girls to war against their bodies in their teens. Fortunately for me, I found my own sense of otherness, and my ambivalence about being a woman, reflected in the work of female writers and musicians who reassured me that, in spite of everything a sexist world tries to throw at the female-bodied, it’s fine not to feel pink, frilly and compliant inside your own head; it’s &lt;span class="caps"&gt;OK&lt;/span&gt; to feel confused, dark, both sexual and non-sexual, unsure of what or who you&amp;nbsp;are.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;At last, you spell it out.  But trans men are not confused and don&amp;#8217;t need you to save&amp;nbsp;them.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;I want to be very clear here: I know transition will be a solution for some gender dysphoric people, although I’m also aware through extensive research that studies have consistently shown that between 60-90% of gender dysphoric teens will grow out of their&amp;nbsp;dysphoria.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Flat-out incorrect.  I assume you&amp;#8217;re referring to &lt;a href="https://www.kqed.org/futureofyou/441784/the-controversial-research-on-desistance-in-transgender-youth"&gt;research&lt;/a&gt; that the bulk (&amp;#8220;65 to 94 percent&amp;#8221;) of dysphoric &lt;em&gt;prepubescent children&lt;/em&gt; will &amp;#8220;grow out of it&amp;#8221; — but if it persists beyond puberty (i.e., into their teens), it&amp;#8217;s &lt;a href="https://books.google.com/books?id=Np8xxP6pcdUC&amp;amp;pg=RA1-PT483"&gt;most likely permanent&lt;/a&gt;.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Again and again I’ve been told to ‘just meet some trans people.’ I have: in addition to a few younger people, who were all adorable, I happen to know a self-described transsexual woman who’s older than I am and wonderful. Although she’s open about her past as a gay man, I’ve always found it hard to think of her as anything other than a woman, and I believe (and certainly hope) she’s completely happy to have&amp;nbsp;transitioned.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Describing them as &amp;#8220;adorable&amp;#8221; does not fill me with confidence that you listened to anything they had to say, especially in light of your repeated attempts to cast trans boys as confused or&amp;nbsp;misled.&lt;/p&gt;
&lt;p&gt;I&amp;#8217;m glad you have 1 trans friend, whose viewpoint or input you manage to not actually mention whatsoever before using her as a foothold to make another &amp;#8220;concerned&amp;#8221;&amp;nbsp;point:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Being older, though, she went through a long and rigorous process of evaluation, psychotherapy and staged transformation. The current explosion of trans activism is urging a removal of almost all the robust systems through which candidates for sex reassignment were once required to&amp;nbsp;pass.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;If you would &amp;#8220;just meet some trans people&amp;#8221;, you would know that the long and rigorous process is torture.  Quite regularly I see tweets — from folks in the &lt;span class="caps"&gt;UK&lt;/span&gt; especially — about having to wait for up to a year or more just to see a gender therapist &lt;em&gt;once&lt;/em&gt;, after which they have to wait &lt;em&gt;even longer&lt;/em&gt; to even begin hormones.  In the &lt;span class="caps"&gt;US&lt;/span&gt;, I&amp;#8217;ve read no end of anecdotes from people who have to perform the right &amp;#8220;kind&amp;#8221; of transness to convince a therapist to write them a referral letter, after who knows how many sessions.  And this is, quite often, after years of &lt;em&gt;internal&lt;/em&gt; debate and questioning.  Years and years of their lives lost&amp;nbsp;forever.&lt;/p&gt;
&lt;p&gt;All of this is predicated, once again, on the idea that trans people just don&amp;#8217;t know what&amp;#8217;s good for&amp;nbsp;themselves.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;A man [sic] who intends to have no surgery and take no hormones may now secure himself [sic] a Gender Recognition Certificate and be a woman in the sight of the law. Many people aren’t aware of&amp;nbsp;this.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;She would &lt;a href="https://www.gov.uk/apply-gender-recognition-certificate"&gt;need&lt;/a&gt; a formal diagnosis and to have lived as a woman for at least two years.  At least as written, a cis man cannot simply show up and get an F stamped on his passport.  I don&amp;#8217;t even know what possible purpose that would&amp;nbsp;serve.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;We’re living through the most misogynistic period I’ve experienced. Back in the 80s, I imagined that my future daughters, should I have any, would have it far better than I ever did, but between the backlash against feminism and a porn-saturated online culture, I believe things have got significantly worse for girls. Never have I seen women denigrated and dehumanised to the extent they are now. From the leader of the free world’s long history of sexual assault accusations and his proud boast of ‘grabbing them by the pussy’, to the incel (‘involuntarily celibate’) movement that rages against women who won’t give them sex, to the trans activists who declare that TERFs need punching and re-educating, men across the political spectrum seem to agree: women are asking for trouble. Everywhere, women are being told to shut up and sit down, or&amp;nbsp;else.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;I cannot believe you are comparing sexual assault and incels — who have committed mass shootings! — to angry trans people tweeting anime screenshots captioned &amp;#8220;shut up&amp;#8221; at you.  &amp;#8220;&lt;span class="caps"&gt;TERF&lt;/span&gt;&amp;#8221; doesn&amp;#8217;t even imply a woman — the most infamous one by far &lt;em&gt;is a man&lt;/em&gt;, Graham&amp;nbsp;Lineham!&lt;/p&gt;
&lt;p&gt;Meanwhile, &lt;em&gt;you&lt;/em&gt; have — &lt;strong&gt;multiple times in this essay&lt;/strong&gt; — suggested that trans boys are misled and the choices they&amp;#8217;ve made for themselves are somehow mistakes.  I know you consider them women, because your exact phrasing was to call them &amp;#8220;girls [sic] being referred for transitioning treatment&amp;#8221; and then reframe their choices as actually being about misogyny.  What kind of feminism is it to decide you know better than people you think are women?  Not even &lt;em&gt;decide&lt;/em&gt;, but take for granted, speak about as though their agency never existed to be dismissed in the first&amp;nbsp;place?&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;I’ve read all the arguments about femaleness not residing in the sexed body, and the assertions that biological women don’t have common experiences, and I find them, too, deeply misogynistic and regressive. It’s also clear that one of the objectives of denying the importance of sex is to erode what some seem to see as the cruelly segregationist idea of women having their own biological realities or – just as threatening – unifying realities that make them a cohesive political class. The hundreds of emails I’ve received in the last few days prove this erosion concerns many others just as much.  It isn’t enough for women to be trans allies. Women must accept and admit that there is no material difference between trans women and&amp;nbsp;themselves.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Who has said that cis women don&amp;#8217;t have common biological experiences?  The issue is that most trans men and some nonbinary folks &lt;em&gt;also&lt;/em&gt; have those experiences (and some cis women don&amp;#8217;t), so if you&amp;#8217;re going to talk about them, why not talk about &lt;em&gt;the experience&lt;/em&gt; instead of saying &amp;#8220;women&amp;#8221; and presuming that everyone will intuit which of a dozen possible facets of womanhood you&amp;#8217;re referring&amp;nbsp;to?&lt;/p&gt;
&lt;p&gt;And if the experience in question is a social one, based on &lt;em&gt;other people&amp;#8217;s&lt;/em&gt; perception of you as a woman, then guess what: loads of trans women &lt;em&gt;will&lt;/em&gt; also have had those&amp;nbsp;experiences.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;But, as many women have said before me, ‘woman’ is not a costume. ‘Woman’ is not an idea in a man’s head. ‘Woman’ is not a pink brain, a liking for Jimmy Choos or any of the other sexist ideas now somehow touted as&amp;nbsp;progressive.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;The women saying those things, anecdotally, appear to have significant overlap with women who criticize trans women for not &amp;#8220;looking&amp;#8221; female enough.  Or who, sadly, misidentify cis women &lt;em&gt;as&lt;/em&gt; trans women for not &amp;#8220;looking&amp;#8221; female enough.  You know, that refined &lt;em&gt;classical&lt;/em&gt;&amp;nbsp;sexism.&lt;/p&gt;
&lt;p&gt;If trans women wear dresses, they&amp;#8217;re treating womanhood as a costume; if they don&amp;#8217;t, they&amp;#8217;re faking&amp;nbsp;it.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Moreover, the ‘inclusive’ language that calls female people ‘menstruators’ and ‘people with vulvas’ strikes many women as dehumanising and demeaning. I understand why trans activists consider this language to be appropriate and kind, but for those of us who’ve had degrading slurs spat at us by violent men, it’s not neutral, it’s hostile and&amp;nbsp;alienating.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Clearly you don&amp;#8217;t understand, as no one is blanket referring to &amp;#8220;female people&amp;#8221; as &amp;#8220;menstruators&amp;#8221;.  The current kerfuffle &lt;em&gt;started&lt;/em&gt; because you commented on an article titled &amp;#8220;Creating a more equal post-&lt;span class="caps"&gt;COVID&lt;/span&gt;-19 world for people who menstruate&amp;#8221;.  It used that phrasing &lt;em&gt;because it was about menstruation&lt;/em&gt; (and was written by three women).  The only person in this whole mess who has tried to reduce women to their body parts is &lt;em&gt;you&lt;/em&gt;, in your initial tweet, insisting that menstruation is a uniquely defining feature of&amp;nbsp;womanhood.&lt;/p&gt;
&lt;p&gt;Moreover, the article is about addressing a women&amp;#8217;s health and women&amp;#8217;s rights issue, and it mentions women frequently, but your only response was to &lt;em&gt;criticize the title&lt;/em&gt; for trying to include the very people — trans men — that you keep trampling in this essay.  I find your choice of priorities increasingly&amp;nbsp;alarming.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;If you could come inside my head and understand what I feel when I read about a trans woman dying at the hands of a violent man, you’d find solidarity and kinship. I have a visceral sense of the terror in which those trans women will have spent their last seconds on earth, because I too have known moments of blind fear when I realised that the only thing keeping me alive was the shaky self-restraint of my&amp;nbsp;attacker.&lt;/p&gt;
&lt;p&gt;I believe the majority of trans-identified people not only pose zero threat to others, but are vulnerable for all the reasons I’ve outlined. Trans people need and deserve protection. Like women, they’re most likely to be killed by sexual partners. Trans women who work in the sex industry, particularly trans women of colour, are at particular risk. Like every other domestic abuse and sexual assault survivor I know, I feel nothing but empathy and solidarity with trans women who’ve been abused by&amp;nbsp;men.&lt;/p&gt;
&lt;p&gt;So I want trans women to be safe. At the same time, I do not want to make natal girls and women less safe. When you throw open the doors of bathrooms and changing rooms to any man who believes or feels he’s a woman – and, as I’ve said, gender confirmation certificates may now be granted without any need for surgery or hormones – then you open the door to any and all men who wish to come inside. That is the simple&amp;nbsp;truth.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;I&amp;#8217;m sorry for what you went through, but these few paragraphs horrify me.  You understand and describe in vivid detail what some of these women go through, how their lives end, how at risk they are, and then immediately segue into how those women should not be given shelter — hell, not even just shelter, but &lt;em&gt;a place to pee&lt;/em&gt; — because someone else might hypothetically abuse&amp;nbsp;it.&lt;/p&gt;
&lt;p&gt;I must be missing something, because this has never made sense to me.  People who commit sexual assault are not especially interested in following the rules, so how is adding another rule meant to dissuade them from this contrived scheme?  If someone is around to police who goes into the bathroom, why could that same person not instead intervene if someone tries to cause&amp;nbsp;harm?&lt;/p&gt;
&lt;p&gt;Anyway, what do you propose instead?  You never say, which seems deeply at odds with your desire for trans women to be safe.  The only alternative I ever hear involves checking identification and chromosomal analysis and all kinds of other absurdity — which is clearly aimed at trans folks and not nefarious men.  Are you fine with the status quo, which is that trans people &lt;em&gt;already&lt;/em&gt; use whatever bathroom they find most appropriate?  Or do you think your trans woman friend should be forced into the men&amp;#8217;s room, surrounded by men?  Without saying one way or the other, you&amp;#8217;re actively encouraging fear and hostility towards people who &lt;em&gt;just want to pee&lt;/em&gt; — and not just towards trans people, but towards anyone who doesn&amp;#8217;t &amp;#8220;look female&amp;nbsp;enough&amp;#8221;.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;On Saturday morning, I read that the Scottish government is proceeding with its controversial gender recognition plans, which will in effect mean that all a man needs to ‘become a woman’ is to say he’s one. To use a very contemporary word, I was ‘triggered’. Ground down by the relentless attacks from trans activists on social media, when I was only there to give children feedback about pictures they’d drawn for my book under lockdown, I spent much of Saturday in a very dark place inside my head, as memories of a serious sexual assault I suffered in my twenties recurred on a loop. That assault happened at a time and in a space where I was vulnerable, and a man capitalised on an opportunity.  I couldn’t shut out those memories and I was finding it hard to contain my anger and disappointment about the way I believe my government is playing fast and loose with womens and girls’&amp;nbsp;safety.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Why did you take it out on the very people you just said you also want to be safe?  Why did you take it out on an article that had little to do with safety and was pushing for better health and privacy?  You&amp;#8217;ve already said you know exactly how your actions will be perceived, so the backlash this time cannot have come as a&amp;nbsp;surprise.&lt;/p&gt;
&lt;p&gt;There was so much opportunity here for talking about cultural expectations and gender roles, how those foster and overlook violence and aggression from boys from a young age, how a lot of societal structures still suggest that men are &amp;#8220;owed&amp;#8221; something by women, or how violence is more broadly glorified in Western culture.  As a world-renowned author who&amp;#8217;s done extensive feminist research, you could surely make an&amp;nbsp;impact.&lt;/p&gt;
&lt;p&gt;Instead, you decided to hurt&amp;nbsp;people.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Late on Saturday evening, scrolling through children’s pictures before I went to bed, I forgot the first rule of Twitter – never, ever expect a nuanced conversation – and reacted to what I felt was degrading language about women. I spoke up about the importance of sex and have been paying the price ever since. I was transphobic, I was a cunt, a bitch, a &lt;span class="caps"&gt;TERF&lt;/span&gt;, I deserved cancelling, punching and death. You are Voldemort said one person, clearly feeling this was the only language I’d&amp;nbsp;understand.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;You offered absolutely no nuance yourself, and this essay has carefully weaved around it the whole time as well.  You, a straight person, co-opted the gay community&amp;#8217;s struggle so you could wield it as a club against trans people — after tossing them Dumbledore as a token afterthought — despite having ties yourself to an &lt;span class="caps"&gt;MP&lt;/span&gt; who has actively tried to erode gay&amp;nbsp;rights.&lt;/p&gt;
&lt;p&gt;But yes, let us talk about Harry Potter and how it reflects your values.  &lt;em&gt;Zero&lt;/em&gt; non-heterosexual characters mentioned within the canon.  But more of interest: where are the women?  The main character, a boy; his mentor and the primary authority figure, a man; the teacher he&amp;#8217;s at odds with, a man; the rival and entourage, all boys; his best friend, a boy; the awkward coward who gets a late redemption arc, a boy; the primary antagonist, a man; the sympathetic adult confidant, a man; the rediscovered long-lost family member, a man; the endlessly regenerating Defense Against the Dark Arts teachers, all men except for the cartoon villain Umbridge.  The Weasleys have seven children; &lt;em&gt;six&lt;/em&gt; are boys.  Two of the Hogwarts founders are men, and two women&amp;#8230;  ah, but the men are the founders of the two plot-important houses.  Vernon is clearly the head of the Dursley family, and their only child is a boy.  On it&amp;nbsp;goes.&lt;/p&gt;
&lt;p&gt;Girls can aspire to be the nerd no one likes (hey, that&amp;#8217;s me!), the insane woman no one believes, the abusive monster, the nurse with no personality, or one of a handful of love interests.  McGonagall is extremely cool and can turn into a cat, I grant you.  And I think there was someone named Bellatrix?  But wasn&amp;#8217;t she a Death&amp;nbsp;Eater?&lt;/p&gt;
&lt;p&gt;I don&amp;#8217;t claim to be an expert on your series; on the contrary, I read them casually when they came out and haven&amp;#8217;t revisited them since.  This is the cast that left an impression on me.  I have published half-hour video games with more female characters than I can name off the top of my head from the entire Harry Potter canon.  Where was your concern for uplifting girls throughout the decade you spent writing the most popular book series in the history of the human race?  Where was your interest in the well-being of gay teens as you dedicated untold pages to descriptions of wizard&amp;nbsp;football?&lt;/p&gt;
&lt;p&gt;I hope that&amp;#8217;s enough&amp;nbsp;nuance.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;It would be so much easier to tweet the approved hashtags – because of course trans rights are human rights and of course trans lives matter – scoop up the woke cookies and bask in a virtue-signalling afterglow. There’s joy, relief and safety in conformity. As Simone de Beauvoir also wrote, “… without a doubt it is more comfortable to endure blind bondage than to work for one’s liberation; the dead, too, are better suited to the earth than the&amp;nbsp;living.”&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;span class="dquo"&gt;&amp;#8220;&lt;/span&gt;Virtue signalling&amp;#8221; is not in itself a bad thing; it is literally the indication to others of what our values are, so others know what we believe and how we are likely to treat them.  Your essay still signals your virtues, as does&amp;nbsp;mine.&lt;/p&gt;
&lt;p&gt;&lt;span class="dquo"&gt;&amp;#8220;&lt;/span&gt;Of course&amp;#8221; trans rights are human rights?  I cannot even tell if this is meant to be serious or sarcastic, with how much seething resentment you&amp;#8217;ve wrapped it in.  Do you also consider your supposed support of lesbians to be &amp;#8220;conformity&amp;#8221;, since that&amp;#8217;s no longer an especially controversial&amp;nbsp;stance?&lt;/p&gt;
&lt;p&gt;This is all outright reactionary rhetoric and you know it.  You are using the very same catchphrases that the incels you so revile use when justifying their hatred for&amp;nbsp;women.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Huge numbers of women are justifiably terrified by the trans activists; I know this because so many have got in touch with me to tell their stories. They’re afraid of doxxing, of losing their jobs or their livelihoods, and of&amp;nbsp;violence.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Who is doxxing people?  I tried to look into this and instead found a list of &lt;span class="caps"&gt;TERF&lt;/span&gt; websites with a prominent warning that they track and doxx and harass trans people; the Rational Wiki asserting that TERFs engage in doxxing; and &lt;a href="https://twitter.com/caseyexplosion/status/1214904615738761216"&gt;this second-hand account&lt;/a&gt; that an ex-&lt;span class="caps"&gt;TERF&lt;/span&gt; was &amp;#8220;threatened with doxing&amp;#8221; by her own allies and &amp;#8220;kept in a perpetual state of&amp;nbsp;fear&amp;#8221;.&lt;/p&gt;
&lt;p&gt;And who on earth is sinking to violence over this?  I find e.g. the &amp;#8220;photo with a gun pointed at the viewer&amp;#8221; phenomenon pretty distasteful, but it doesn&amp;#8217;t seem to be unique to this issue, it&amp;#8217;s not an especially credible threat of violence, and it&amp;#8217;s the closest to actual violence I&amp;#8217;ve ever heard of here.  Surely, if anyone had come to blows, we&amp;#8217;d never hear the end of&amp;nbsp;it?&lt;/p&gt;
&lt;p&gt;I note that Forstater&amp;#8217;s contract wasn&amp;#8217;t renewed because, as best as we can tell, she made her coworkers uncomfortable and the work environment hostile.  Meanwhile, trans people can be (and are) fired for simply &lt;em&gt;existing&lt;/em&gt;.  Citing this as a fear people have of trans people, as though they were some large shadowy conspiracy, feels fairly&amp;nbsp;tasteless.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;But endlessly unpleasant as its constant targeting of me has been, I refuse to bow down to a movement that I believe is doing demonstrable harm in seeking to erode ‘woman’ as a political and biological class and offering cover to predators like few before it. I stand alongside the brave women and men, gay, straight and trans, who’re standing up for freedom of speech and thought, and for the rights and safety of some of the most vulnerable in our society: young gay kids, fragile teenagers, and women who’re reliant on and wish to retain their single sex spaces. Polls show those women are in the vast majority, and exclude only those privileged or lucky enough never to have come up against male violence or sexual assault, and who’ve never troubled to educate themselves on how prevalent it&amp;nbsp;is.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;By &amp;#8220;young gay kids&amp;#8221; and &amp;#8220;fragile teenagers&amp;#8221;, are you once again obliquely referring to young trans people who you take to be merely confused?  What of &lt;em&gt;their&lt;/em&gt; freedom of thought, of their right to decide who they are for themselves without seeing you use them as ammunition against other people like them?  What impact do you think that will have on them,&amp;nbsp;exactly?&lt;/p&gt;
&lt;p&gt;Falling back on &amp;#8220;freedom of speech&amp;#8221; to defend one&amp;#8217;s own hurtful speech is another reactionary talking point; when you cannot defend your speech on its own merits, you can only defend that it is not literally illegal to&amp;nbsp;say.&lt;/p&gt;
&lt;p&gt;What polls are you finding?  &lt;a href="https://www.pinknews.co.uk/2019/04/18/two-thirds-support-trans-bathrooms-gender-identity/"&gt;26%&lt;/a&gt; is not a vast majority, and it&amp;#8217;s troubling that you proactively dismiss the women who disagree with you as aloof and uninformed.  What kind of feminism is&amp;nbsp;that?&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;The one thing that gives me hope is that the women who can protest and organise, are doing so, and they have some truly decent men and trans people alongside them. Political parties seeking to appease the loudest voices in this debate are ignoring women’s concerns at their peril. In the &lt;span class="caps"&gt;UK&lt;/span&gt;, women are reaching out to each other across party lines, concerned about the erosion of their hard-won rights and widespread intimidation. None of the gender critical women I’ve talked to hates trans people; on the contrary. Many of them became interested in this issue in the first place out of concern for trans youth, and they’re hugely sympathetic towards trans adults who simply want to live their lives, but who’re facing a backlash for a brand of activism they don’t endorse. The supreme irony is that the attempt to silence women with the word ‘&lt;span class="caps"&gt;TERF&lt;/span&gt;’ may have pushed more young women towards radical feminism than the movement’s seen in&amp;nbsp;decades.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Absolute bullshit.  You&amp;#8217;ve consistently brushed off or spoken for women and trans men who disagree with you in this post alone, but frame your own stance as though it were shared by all women.  Two women you&amp;#8217;ve mentioned by name and made a point of supporting — Maya Forstater and Magdalen Berns — have said some &lt;em&gt;astonishingly&lt;/em&gt; cruel things about trans people as blanket remarks, so I can only interpret their &amp;#8220;non-hate&amp;#8221; in the same way as people repeatedly told my younger self that they loved me but I would burn for all eternity if I kissed both boys and girls.  If their &amp;#8220;concern&amp;#8221; for trans youth is anything like yours, then they&amp;#8217;re only interested in trying to berate trans youth into not wanting to be trans any more — yet again, no different from how homophobia played&amp;nbsp;out.&lt;/p&gt;
&lt;p&gt;And, hang on, they&amp;#8217;re hugely sympathetic towards trans adults who&amp;#8217;re facing backlash?  You must be joking.  They — and you — &lt;strong&gt;&lt;span class="caps"&gt;ARE&lt;/span&gt;&lt;/strong&gt; the backlash!  What good is sympathy from the very people who are deliberately hurting&amp;nbsp;you?&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;The last thing I want to say is this. I haven’t written this essay in the hope that anybody will get out a violin for me, not even a teeny-weeny one. I’m extraordinarily fortunate; I’m a survivor, certainly not a victim. I’ve only mentioned my past because, like every other human being on this planet, I have a complex backstory, which shapes my fears, my interests and my opinions. I never forget that inner complexity when I’m creating a fictional character and I certainly never forget it when it comes to trans&amp;nbsp;people.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;You&amp;#8217;ve done so multiple times in this essay alone, and your heroes do it on a pretty consistent basis.  What an insult to everyone who read&amp;nbsp;this.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;All I’m asking – all I want – is for similar empathy, similar understanding, to be extended to the many millions of women whose sole crime is wanting their concerns to be heard without receiving threats and&amp;nbsp;abuse.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;In the entirety of this essay, you didn&amp;#8217;t even mention a single concrete concern.  You did some vague fearmongering about how a cis man could get a piece of paper saying he&amp;#8217;s a woman, and that&amp;#8217;s all.  Meanwhile, you managed to repeatedly misgender and patronize trans boys; paint trans adults as a nefarious political movement trying to &amp;#8220;persuade&amp;#8221; children; cite multiple people who&amp;#8217;ve been fiercely nasty towards trans people as a whole, while avoiding mentioning what they actually did so you could frame them as innocent victims; invoke multiple homophobic and reactionary tropes with a quick coat of paint slapped on top; present &amp;#8220;parents who wish their children were cis&amp;#8221; as though it were a diagnosed phenomenon; and generally checked off every possible &lt;span class="caps"&gt;TERF&lt;/span&gt; talking point while smiling kindly the whole&amp;nbsp;time.&lt;/p&gt;
&lt;p&gt;You&amp;#8217;re saying things you know are actively hurtful in the name of preventing a hypothetical harm that is so nebulous you can&amp;#8217;t even describe&amp;nbsp;it.&lt;/p&gt;
&lt;p&gt;This&amp;nbsp;sucks.&lt;/p&gt;</content><category term="blog"></category><category term="personal"></category><category term="culture"></category><category term="gender"></category></entry><entry><title>Old CSS, new CSS</title><link href="https://eev.ee/blog/2020/02/01/old-css-new-css/" rel="alternate"></link><published>2020-02-01T23:21:00-08:00</published><updated>2020-02-01T23:21:00-08:00</updated><author><name>Eevee</name></author><id>tag:eev.ee,2020-02-01:/blog/2020/02/01/old-css-new-css/</id><summary type="html">&lt;p&gt;I first got into web design/development in the late 90s, and only as I type this sentence do I realize how long ago that was.&lt;/p&gt;
&lt;p&gt;And boy, it was horrendous.  I mean, being able to make stuff and put it online where other people could see it was pretty slick, but we did not have very much to work with.&lt;/p&gt;
&lt;p&gt;I’ve been taking for granted that &lt;em&gt;most&lt;/em&gt; folks doing web stuff still remember those days, or at least the decade that followed, but I think that assumption might be a wee bit out of date.  Some time ago I encountered a &lt;a href="https://twitter.com/keinegurke_/status/1162309192855822339"&gt;tweet&lt;/a&gt; marvelling at what we had to do without &lt;code&gt;border-radius&lt;/code&gt;.  I still remember waiting with bated breath for it to be unprefixed!&lt;/p&gt;
&lt;p&gt;But then, I suspect I also know a number of folks who only tried web design in the old days, and assume nothing about it has changed since.&lt;/p&gt;
&lt;p&gt;I’m here to tell &lt;em&gt;all&lt;/em&gt; of you to get off my lawn.  Here’s a history of &lt;span class="caps"&gt;CSS&lt;/span&gt; and web design, as I remember it.&lt;/p&gt;
</summary><content type="html">&lt;p&gt;I first got into web design/development in the late 90s, and only as I type this sentence do I realize how long ago that&amp;nbsp;was.&lt;/p&gt;
&lt;p&gt;And boy, it was horrendous.  I mean, being able to make stuff and put it online where other people could see it was pretty slick, but we did not have very much to work&amp;nbsp;with.&lt;/p&gt;
&lt;p&gt;I&amp;#8217;ve been taking for granted that &lt;em&gt;most&lt;/em&gt; folks doing web stuff still remember those days, or at least the decade that followed, but I think that assumption might be a wee bit out of date.  Some time ago I encountered a &lt;a href="https://twitter.com/keinegurke_/status/1162309192855822339"&gt;tweet&lt;/a&gt; marvelling at what we had to do without &lt;code&gt;border-radius&lt;/code&gt;.  I still remember waiting with bated breath for it to be&amp;nbsp;unprefixed!&lt;/p&gt;
&lt;p&gt;But then, I suspect I also know a number of folks who only tried web design in the old days, and assume nothing about it has changed&amp;nbsp;since.&lt;/p&gt;
&lt;p&gt;I&amp;#8217;m here to tell &lt;em&gt;all&lt;/em&gt; of you to get off my lawn.  Here&amp;#8217;s a history of &lt;span class="caps"&gt;CSS&lt;/span&gt; and web design, as I remember&amp;nbsp;it.&lt;/p&gt;


&lt;hr /&gt;
&lt;p&gt;(Please bear in mind that this post is a fine blend of memory and research, so I can&amp;#8217;t guarantee any of it is actually correct, &lt;em&gt;especially&lt;/em&gt; the bits about causality.  You may want to try the &lt;a href="https://www.w3.org/Style/CSS20/history.html"&gt;&lt;span class="caps"&gt;W3C&lt;/span&gt;&amp;#8217;s history of &lt;span class="caps"&gt;CSS&lt;/span&gt;&lt;/a&gt;, which is considerably shorter, has a better chance of matching reality, and contains significantly less&amp;nbsp;swearing.)&lt;/p&gt;
&lt;p&gt;(Also, this would benefit greatly from more diagrams, but it took long enough just to &lt;em&gt;write&lt;/em&gt;.)&lt;/p&gt;
&lt;h2 id="the-very-early-days"&gt;&lt;a class="toclink" href="#the-very-early-days"&gt;The very early&amp;nbsp;days&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;In the beginning, there was no &lt;span class="caps"&gt;CSS&lt;/span&gt;.&lt;/p&gt;
&lt;p&gt;This was very&amp;nbsp;bad.&lt;/p&gt;
&lt;p&gt;My favorite artifact of this era is the book that taught me &lt;span class="caps"&gt;HTML&lt;/span&gt;: O&amp;#8217;Reilly&amp;#8217;s &lt;a href="https://isbnsearch.org/isbn/9781565924925"&gt;&lt;span class="caps"&gt;HTML&lt;/span&gt;: The Definitive Guide&lt;/a&gt;, published in several editions in the mid to late 90s.  The book was indeed about &lt;em&gt;&lt;span class="caps"&gt;HTML&lt;/span&gt;&lt;/em&gt;, with no mention of &lt;span class="caps"&gt;CSS&lt;/span&gt; at all.  I don&amp;#8217;t have it any more and can&amp;#8217;t readily find screenshots online, but here&amp;#8217;s a page from &lt;span class="caps"&gt;HTML&lt;/span&gt; &lt;span class="amp"&gt;&amp;amp;&lt;/span&gt; &lt;span class="caps"&gt;XHTML&lt;/span&gt;: The Definitive Guide, which seems to be a revision (I&amp;#8217;ll get to &lt;span class="caps"&gt;XHTML&lt;/span&gt; later) with much the same style.  Here, then, is the cutting-edge web design advice of&amp;nbsp;199X:&lt;/p&gt;
&lt;div class="prose-full-illustration"&gt;
&lt;img src="https://eev.ee/media/2020-02-css/html-definitive-guide.png" alt="Screenshot of a plain website in IE, with plain black text on a white background with a simple image"&gt;
&lt;/div&gt;

&lt;p&gt;&lt;span class="dquo"&gt;&amp;#8220;&lt;/span&gt;&lt;em&gt;Clearly delineate headers and footers with horizontal rules.&lt;/em&gt;&amp;#8221;&lt;/p&gt;
&lt;p&gt;No, that&amp;#8217;s not a &lt;code&gt;border-top&lt;/code&gt;.  That&amp;#8217;s an &lt;code&gt;&amp;lt;hr&amp;gt;&lt;/code&gt;.  The page title is almost certainly centered with, well, &lt;code&gt;&amp;lt;center&amp;gt;&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;The page uses the default text color, background, and font.  Partly because this is a guidebook introducing concepts one at a time; partly because the book was printed in black and white; and partly, I&amp;#8217;m sure, because it reflected the reality that coloring anything was a huge pain in the&amp;nbsp;ass.&lt;/p&gt;
&lt;p&gt;Let&amp;#8217;s say you wanted all your &lt;code&gt;&amp;lt;h1&amp;gt;&lt;/code&gt;s to be red, across your entire site.  You had to do&amp;nbsp;this:&lt;/p&gt;
&lt;table class="highlighttable"&gt;&lt;tr&gt;&lt;td class="linenos"&gt;&lt;div class="linenodiv"&gt;&lt;pre&gt;&lt;span class="normal"&gt;1&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/td&gt;&lt;td class="code"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;H1&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;FONT&lt;/span&gt; &lt;span class="na"&gt;COLOR&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;red&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;...&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;FONT&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;H1&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;p&gt;&amp;#8230;&lt;em&gt;every single goddamn time&lt;/em&gt;.  Hope you never decide to switch to&amp;nbsp;blue!&lt;/p&gt;
&lt;p&gt;Oh, and everyone wrote &lt;span class="caps"&gt;HTML&lt;/span&gt; tags in all caps.  I don&amp;#8217;t remember why we all thought that was a good idea.  Maybe this was before syntax highlighting in text editors was very common (read: I was 12 and using Notepad), and uppercase tags were easier to distinguish from body&amp;nbsp;text.&lt;/p&gt;
&lt;p&gt;Keeping your site consistent was thus something of a nightmare.  One solution was to simply not style anything, which a lot of folks did.  This was nice, in some ways, since browsers let you change those defaults, so you could read the Web how you&amp;nbsp;wanted.&lt;/p&gt;
&lt;p&gt;A clever alternate solution, which I remember showing up in a lot of Geocities sites, was to simply give every page a completely different visual style.  Fuck it, right?  Just do whatever you want on each new&amp;nbsp;page.&lt;/p&gt;
&lt;p&gt;That trend was quite possibly the height of web&amp;nbsp;design.&lt;/p&gt;
&lt;p&gt;Damn, I miss those days.  There were no big walled gardens, no Twitter or Facebook.  If you had anything to say to anyone, you had to put together your own website.  It was &lt;em&gt;amazing&lt;/em&gt;.  No one knew what they were doing; I&amp;#8217;d wager that the vast majority of web designers at the time were clueless hobbyist tweens (like me) all copying from other clueless hobbyist tweens.  Half the Web was fan portals about Animorphs, with inexplicable splash pages warning you that their site worked best if you had a 640×480 screen.  (Any 12-year-old with insufficient resolution should, presumably, buy a new monitor with their allowance.)  Everyone who was cool and in the know used Internet Explorer 3, the most advanced browser, but some losers still used Netscape Navigator so you had to put a &amp;#8220;Best in &lt;span class="caps"&gt;IE&lt;/span&gt;&amp;#8221; animated &lt;span class="caps"&gt;GIF&lt;/span&gt; on your splash page&amp;nbsp;too.&lt;/p&gt;
&lt;p&gt;This was also the era of &amp;#8220;web-safe colors&amp;#8221; — a palette of 216 colors, where every channel was one of &lt;code&gt;00&lt;/code&gt;, &lt;code&gt;33&lt;/code&gt;, &lt;code&gt;66&lt;/code&gt;, &lt;code&gt;99&lt;/code&gt;, &lt;code&gt;cc&lt;/code&gt;, or &lt;code&gt;ff&lt;/code&gt; — which existed because some people still had 256-color monitors!  The things we take for granted now, like 24-bit&amp;nbsp;color.&lt;/p&gt;
&lt;p&gt;In fact, a &lt;em&gt;lot&lt;/em&gt; of stuff we take for granted now was still a strange and untamed problem space.  You want to have the same navigation on every page on your website?  Okay, no problem: copy/paste it onto each page.  When you update it, be sure to update every page — but most likely you&amp;#8217;ll forget some, and your whole site will become an archaeological dig into itself, with strata of increasingly bitrotted&amp;nbsp;pages.&lt;/p&gt;
&lt;p&gt;Much easier was to use &lt;em&gt;frames&lt;/em&gt;, meaning the browser window is split into a grid and a different page loads in each section&amp;#8230;  but then people would get confused if they landed on an individual page without the frames, as was common when coming from a search engine like AltaVista.  (I can&amp;#8217;t believe I&amp;#8217;m explaining frames, but no one has used them since like 2001.  You know iframes?  The &amp;#8220;i&amp;#8221; is for &lt;em&gt;inline&lt;/em&gt;, to distinguish them from &lt;em&gt;regular&lt;/em&gt; frames, which take up the entire&amp;nbsp;viewport.)&lt;/p&gt;
&lt;p&gt;&lt;span class="caps"&gt;PHP&lt;/span&gt; wasn&amp;#8217;t even called that yet, and nobody had heard of it.  This weird &amp;#8220;Perl&amp;#8221; and &amp;#8220;&lt;span class="caps"&gt;CGI&lt;/span&gt;&amp;#8221; thing was really strange and hard to understand, and it didn&amp;#8217;t work on your own computer, and the errors were hard to find and diagnose, and anyway Geocities didn&amp;#8217;t support it.  If you were &lt;em&gt;really&lt;/em&gt; lucky and smart, your web host used Apache, and you could use its &amp;#8220;server side include&amp;#8221; syntax to do something like&amp;nbsp;this:&lt;/p&gt;
&lt;table class="highlighttable"&gt;&lt;tr&gt;&lt;td class="linenos"&gt;&lt;div class="linenodiv"&gt;&lt;pre&gt;&lt;span class="normal"&gt; 1&lt;/span&gt;
&lt;span class="normal"&gt; 2&lt;/span&gt;
&lt;span class="normal"&gt; 3&lt;/span&gt;
&lt;span class="normal"&gt; 4&lt;/span&gt;
&lt;span class="normal"&gt; 5&lt;/span&gt;
&lt;span class="normal"&gt; 6&lt;/span&gt;
&lt;span class="normal"&gt; 7&lt;/span&gt;
&lt;span class="normal"&gt; 8&lt;/span&gt;
&lt;span class="normal"&gt; 9&lt;/span&gt;
&lt;span class="normal"&gt;10&lt;/span&gt;
&lt;span class="normal"&gt;11&lt;/span&gt;
&lt;span class="normal"&gt;12&lt;/span&gt;
&lt;span class="normal"&gt;13&lt;/span&gt;
&lt;span class="normal"&gt;14&lt;/span&gt;
&lt;span class="normal"&gt;15&lt;/span&gt;
&lt;span class="normal"&gt;16&lt;/span&gt;
&lt;span class="normal"&gt;17&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/td&gt;&lt;td class="code"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;BODY&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;TABLE&lt;/span&gt; &lt;span class="na"&gt;WIDTH&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;100%&lt;/span&gt; &lt;span class="na"&gt;BORDER&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;0&lt;/span&gt; &lt;span class="na"&gt;CELLSPACING&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;8&lt;/span&gt; &lt;span class="na"&gt;CELLPADDING&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;0&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
        &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;TR&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
            &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;TD&lt;/span&gt; &lt;span class="na"&gt;COLSPAN&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;2&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
                &lt;span class="cm"&gt;&amp;lt;!--#include virtual=&amp;quot;/header.html&amp;quot; --&amp;gt;&lt;/span&gt; 
            &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;TD&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
        &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;TR&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
        &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;TR&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
            &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;TD&lt;/span&gt; &lt;span class="na"&gt;WIDTH&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;20%&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
                &lt;span class="cm"&gt;&amp;lt;!--#include virtual=&amp;quot;/navigation.html&amp;quot; --&amp;gt;&lt;/span&gt; 
            &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;TD&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
            &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;TD&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
                (actual page content goes here)
            &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;TD&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
        &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;TR&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;TABLE&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;BODY&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;p&gt;&lt;em&gt;Mwah.&lt;/em&gt;  Beautiful.  Apache would see the special comments, paste in the contents of the referenced files, and you&amp;#8217;re off to the races.  The downside was that when you wanted to work on your site, all the navigation was missing, because you were doing it on your regular computer without Apache, and your web browser thought those were just regular &lt;span class="caps"&gt;HTML&lt;/span&gt; comments.  It was impossible to install Apache, of course, because you had a &lt;em&gt;computer&lt;/em&gt;, not a &lt;em&gt;server&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;Sadly, that&amp;#8217;s all gone now — paved over by homogenous timelines where anything that wasn&amp;#8217;t made this week is old news and long forgotten.  The web was supposed to make information eternal, but instead, so much of it became ephemeral.  I miss when virtually everyone I knew had their own website.  Having a Twitter and an Instagram as your entire online presence is a poor&amp;nbsp;substitute.&lt;/p&gt;
&lt;p&gt;&amp;#8230;&lt;/p&gt;
&lt;p&gt;So, let&amp;#8217;s look at the Space Jam&amp;nbsp;website.&lt;/p&gt;
&lt;h2 id="case-study-space-jam"&gt;&lt;a class="toclink" href="#case-study-space-jam"&gt;Case study: Space&amp;nbsp;Jam&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Space Jam, if you&amp;#8217;re not aware, is the greatest movie of all time.  It documents Bugs Bunny&amp;#8217;s extremely short-lived basketball career, playing alongside a live action Michael Jordan to save the planet from aliens for some reason.  It was followed by a series of very successful and critically acclaimed &lt;a href="https://www.talesofgames.com/related_game/barkley-shut-up-jam-gaiden/"&gt;&lt;span class="caps"&gt;RPG&lt;/span&gt; spinoffs&lt;/a&gt;, which describe the fallout of the Space Jam and are extremely&amp;nbsp;canon.&lt;/p&gt;
&lt;p&gt;And we are truly blessed, for 24 years after it came out, its website is &lt;a href="https://www.spacejam.com/1996/"&gt;&lt;span class="caps"&gt;STILL&lt;/span&gt; &lt;span class="caps"&gt;UP&lt;/span&gt;&lt;/a&gt;.  We can explore the pinnacle of 1996 web design, right here, right&amp;nbsp;now.&lt;/p&gt;
&lt;p&gt;First, notice that every page of this site is a static page.  Not only that, but it&amp;#8217;s a static page ending in &lt;code&gt;.htm&lt;/code&gt; rather than &lt;code&gt;.html&lt;/code&gt;, because people on Windows versions before 95 were still beholden to 8.3 filenames.  Not sure why that mattered in a &lt;span class="caps"&gt;URL&lt;/span&gt;, as if you were going to run Windows 3.11 on a Web server, but there you&amp;nbsp;go.&lt;/p&gt;
&lt;p&gt;The &lt;span class="caps"&gt;CSS&lt;/span&gt; for the splash page looks like&amp;nbsp;this:&lt;/p&gt;
&lt;table class="highlighttable"&gt;&lt;tr&gt;&lt;td class="linenos"&gt;&lt;div class="linenodiv"&gt;&lt;pre&gt;&lt;span class="normal"&gt;1&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/td&gt;&lt;td class="code"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;body&lt;/span&gt; &lt;span class="na"&gt;bgcolor&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;#000000&amp;quot;&lt;/span&gt; &lt;span class="na"&gt;background&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;img/bg_stars.gif&amp;quot;&lt;/span&gt; &lt;span class="na"&gt;text&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;#ff0000&amp;quot;&lt;/span&gt; &lt;span class="na"&gt;link&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;#ff4c4c&amp;quot;&lt;/span&gt; &lt;span class="na"&gt;vlink&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;#ff4c4c&amp;quot;&lt;/span&gt; &lt;span class="na"&gt;alink&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;#ff4c4c&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;p&gt;Haha, just kidding!  What the fuck is &lt;span class="caps"&gt;CSS&lt;/span&gt;?  Space Jam predates it by a month.  (I do see a single line in the page source, but I&amp;#8217;m pretty sure that was added much later to style some legally obligatory policy&amp;nbsp;links.)&lt;/p&gt;
&lt;p&gt;Notice the extremely precise positioning of these navigation links.  This feat was accomplished the same way everyone did everything in 1996: with&amp;nbsp;tables.&lt;/p&gt;
&lt;p&gt;In fact, tables have one functional advantage over &lt;span class="caps"&gt;CSS&lt;/span&gt; for layout, which was very important in those days, and not only because &lt;span class="caps"&gt;CSS&lt;/span&gt; didn&amp;#8217;t exist yet.  You see, you can ctrl-click to select a table &lt;em&gt;cell&lt;/em&gt; and even drag around to select all of them, which shows you how the cells are arranged and functions as a super retro layout debugger.  This was great because the first meaningful web debug tool, &lt;a href="https://en.wikipedia.org/wiki/Firebug_%28software%29"&gt;Firebug&lt;/a&gt;, wasn&amp;#8217;t released until 2006 — a whole decade&amp;nbsp;later!&lt;/p&gt;
&lt;div class="prose-full-illustration"&gt;
&lt;img src="https://eev.ee/media/2020-02-css/space-jam-landing-table-cells.png" alt="Screenshot of the Space Jam website with the navigation table's cells selected, showing how the layout works"&gt;
&lt;/div&gt;

&lt;p&gt;The markup for this table is overflowing with inexplicable blank lines, but with those removed, it looks like&amp;nbsp;this:&lt;/p&gt;
&lt;table class="highlighttable"&gt;&lt;tr&gt;&lt;td class="linenos"&gt;&lt;div class="linenodiv"&gt;&lt;pre&gt;&lt;span class="normal"&gt; 1&lt;/span&gt;
&lt;span class="normal"&gt; 2&lt;/span&gt;
&lt;span class="normal"&gt; 3&lt;/span&gt;
&lt;span class="normal"&gt; 4&lt;/span&gt;
&lt;span class="normal"&gt; 5&lt;/span&gt;
&lt;span class="normal"&gt; 6&lt;/span&gt;
&lt;span class="normal"&gt; 7&lt;/span&gt;
&lt;span class="normal"&gt; 8&lt;/span&gt;
&lt;span class="normal"&gt; 9&lt;/span&gt;
&lt;span class="normal"&gt;10&lt;/span&gt;
&lt;span class="normal"&gt;11&lt;/span&gt;
&lt;span class="normal"&gt;12&lt;/span&gt;
&lt;span class="normal"&gt;13&lt;/span&gt;
&lt;span class="normal"&gt;14&lt;/span&gt;
&lt;span class="normal"&gt;15&lt;/span&gt;
&lt;span class="normal"&gt;16&lt;/span&gt;
&lt;span class="normal"&gt;17&lt;/span&gt;
&lt;span class="normal"&gt;18&lt;/span&gt;
&lt;span class="normal"&gt;19&lt;/span&gt;
&lt;span class="normal"&gt;20&lt;/span&gt;
&lt;span class="normal"&gt;21&lt;/span&gt;
&lt;span class="normal"&gt;22&lt;/span&gt;
&lt;span class="normal"&gt;23&lt;/span&gt;
&lt;span class="normal"&gt;24&lt;/span&gt;
&lt;span class="normal"&gt;25&lt;/span&gt;
&lt;span class="normal"&gt;26&lt;/span&gt;
&lt;span class="normal"&gt;27&lt;/span&gt;
&lt;span class="normal"&gt;28&lt;/span&gt;
&lt;span class="normal"&gt;29&lt;/span&gt;
&lt;span class="normal"&gt;30&lt;/span&gt;
&lt;span class="normal"&gt;31&lt;/span&gt;
&lt;span class="normal"&gt;32&lt;/span&gt;
&lt;span class="normal"&gt;33&lt;/span&gt;
&lt;span class="normal"&gt;34&lt;/span&gt;
&lt;span class="normal"&gt;35&lt;/span&gt;
&lt;span class="normal"&gt;36&lt;/span&gt;
&lt;span class="normal"&gt;37&lt;/span&gt;
&lt;span class="normal"&gt;38&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/td&gt;&lt;td class="code"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;table&lt;/span&gt; &lt;span class="na"&gt;width&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;500&lt;/span&gt; &lt;span class="na"&gt;border&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;0&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;TR&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;TD&lt;/span&gt; &lt;span class="na"&gt;colspan&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;5&lt;/span&gt; &lt;span class="na"&gt;align&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;right&lt;/span&gt; &lt;span class="na"&gt;valign&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;top&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;td&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;tr&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;tr&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;td&lt;/span&gt; &lt;span class="na"&gt;colspan&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;2&lt;/span&gt; &lt;span class="na"&gt;align&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;right&lt;/span&gt; &lt;span class="na"&gt;valign&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;middle&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;br&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;br&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;br&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;a&lt;/span&gt; &lt;span class="na"&gt;href&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;cmp/pressbox/pressboxframes.html&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;img&lt;/span&gt; &lt;span class="na"&gt;src&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;img/p-pressbox.gif&amp;quot;&lt;/span&gt; &lt;span class="na"&gt;height&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;56&lt;/span&gt; &lt;span class="na"&gt;width&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;131&lt;/span&gt; &lt;span class="na"&gt;alt&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;Press Box Shuttle&amp;quot;&lt;/span&gt; &lt;span class="na"&gt;border&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;0&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;a&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;td&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;td&lt;/span&gt; &lt;span class="na"&gt;align&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;center&lt;/span&gt; &lt;span class="na"&gt;valign&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;middle&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;a&lt;/span&gt; &lt;span class="na"&gt;href&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;cmp/jamcentral/jamcentralframes.html&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;img&lt;/span&gt; &lt;span class="na"&gt;src&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;img/p-jamcentral.gif&amp;quot;&lt;/span&gt; &lt;span class="na"&gt;height&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;67&lt;/span&gt; &lt;span class="na"&gt;width&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;55&lt;/span&gt; &lt;span class="na"&gt;alt&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;Jam Central&amp;quot;&lt;/span&gt; &lt;span class="na"&gt;border&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;0&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;a&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;td&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;td&lt;/span&gt; &lt;span class="na"&gt;align&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;center&lt;/span&gt; &lt;span class="na"&gt;valign&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;top&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;a&lt;/span&gt; &lt;span class="na"&gt;href&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;cmp/bball/bballframes.html&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;img&lt;/span&gt; &lt;span class="na"&gt;src&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;img/p-bball.gif&amp;quot;&lt;/span&gt; &lt;span class="na"&gt;height&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;62&lt;/span&gt; &lt;span class="na"&gt;width&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;62&lt;/span&gt; &lt;span class="na"&gt;alt&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;Planet B-Ball&amp;quot;&lt;/span&gt; &lt;span class="na"&gt;border&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;0&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;a&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;td&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;td&lt;/span&gt; &lt;span class="na"&gt;align&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;center&lt;/span&gt; &lt;span class="na"&gt;valign&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;bottom&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;br&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;br&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;a&lt;/span&gt; &lt;span class="na"&gt;href&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;cmp/tunes/tunesframes.html&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;img&lt;/span&gt; &lt;span class="na"&gt;src&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;img/p-lunartunes.gif&amp;quot;&lt;/span&gt; &lt;span class="na"&gt;height&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;77&lt;/span&gt; &lt;span class="na"&gt;width&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;95&lt;/span&gt; &lt;span class="na"&gt;alt&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;Lunar Tunes&amp;quot;&lt;/span&gt; &lt;span class="na"&gt;border&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;0&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;a&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;td&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;tr&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;tr&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;td&lt;/span&gt; &lt;span class="na"&gt;align&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;middle&lt;/span&gt; &lt;span class="na"&gt;valign&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;top&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;br&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;br&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;a&lt;/span&gt; &lt;span class="na"&gt;href&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;cmp/lineup/lineupframes.html&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;img&lt;/span&gt; &lt;span class="na"&gt;src&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;img/p-lineup.gif&amp;quot;&lt;/span&gt; &lt;span class="na"&gt;height&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;52&lt;/span&gt; &lt;span class="na"&gt;width&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;63&lt;/span&gt; &lt;span class="na"&gt;alt&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;The Lineup&amp;quot;&lt;/span&gt; &lt;span class="na"&gt;border&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;0&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;a&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;td&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;td&lt;/span&gt; &lt;span class="na"&gt;colspan&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;3&lt;/span&gt; &lt;span class="na"&gt;rowspan&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;2&lt;/span&gt; &lt;span class="na"&gt;align&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;right&lt;/span&gt; &lt;span class="na"&gt;valign&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;middle&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;img&lt;/span&gt; &lt;span class="na"&gt;src&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;img/p-jamlogo.gif&amp;quot;&lt;/span&gt; &lt;span class="na"&gt;height&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;165&lt;/span&gt; &lt;span class="na"&gt;width&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;272&lt;/span&gt; &lt;span class="na"&gt;alt&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;Space Jam&amp;quot;&lt;/span&gt; &lt;span class="na"&gt;border&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;0&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;td&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;td&lt;/span&gt; &lt;span class="na"&gt;align&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;right&lt;/span&gt; &lt;span class="na"&gt;valign&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;bottom&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;a&lt;/span&gt; &lt;span class="na"&gt;href&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;cmp/jump/jumpframes.html&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;img&lt;/span&gt; &lt;span class="na"&gt;src&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;img/p-jump.gif&amp;quot;&lt;/span&gt; &lt;span class="na"&gt;height&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;52&lt;/span&gt; &lt;span class="na"&gt;width&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;58&lt;/span&gt; &lt;span class="na"&gt;alt&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;Jump Station&amp;quot;&lt;/span&gt; &lt;span class="na"&gt;border&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;0&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;a&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;td&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;tr&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
...
&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;table&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;p&gt;That&amp;#8217;s the first two rows, including the logo.  You get the idea.  Everything is laid out with &lt;code&gt;align&lt;/code&gt; and &lt;code&gt;valign&lt;/code&gt; on table cells; &lt;code&gt;rowspan&lt;/code&gt;s and &lt;code&gt;colspan&lt;/code&gt;s are used frequently; and there are some &lt;code&gt;&amp;lt;br&amp;gt;&lt;/code&gt;s thrown in for good measure, to adjust vertical positioning by one line-height at a&amp;nbsp;time.&lt;/p&gt;
&lt;p&gt;Other fantastic artifacts to be found on this page include this header, which contains Apache &lt;span class="caps"&gt;SSI&lt;/span&gt; syntax!  This must&amp;#8217;ve quietly broken when the site was moved over the years; it&amp;#8217;s currently hosted on Amazon S3.  You know, Amazon?  The&amp;nbsp;bookstore?&lt;/p&gt;
&lt;table class="highlighttable"&gt;&lt;tr&gt;&lt;td class="linenos"&gt;&lt;div class="linenodiv"&gt;&lt;pre&gt;&lt;span class="normal"&gt;1&lt;/span&gt;
&lt;span class="normal"&gt;2&lt;/span&gt;
&lt;span class="normal"&gt;3&lt;/span&gt;
&lt;span class="normal"&gt;4&lt;/span&gt;
&lt;span class="normal"&gt;5&lt;/span&gt;
&lt;span class="normal"&gt;6&lt;/span&gt;
&lt;span class="normal"&gt;7&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/td&gt;&lt;td class="code"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;table&lt;/span&gt; &lt;span class="na"&gt;border&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;0&lt;/span&gt; &lt;span class="na"&gt;cellpadding&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;0&lt;/span&gt; &lt;span class="na"&gt;cellspacing&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;0&lt;/span&gt; &lt;span class="na"&gt;width&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;488&lt;/span&gt; &lt;span class="na"&gt;height&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;60&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;tr&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;td&lt;/span&gt; &lt;span class="na"&gt;align&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;center&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;&lt;span class="cm"&gt;&amp;lt;!--#include virtual=&amp;quot;html.ng/site=spacejam&amp;amp;type=movie&amp;amp;home=no&amp;amp;size=234&amp;amp;page.allowcompete=no&amp;quot;--&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;td&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;td&lt;/span&gt; &lt;span class="na"&gt;align&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;center&amp;quot;&lt;/span&gt; &lt;span class="na"&gt;width&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;20&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;td&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;td&lt;/span&gt; &lt;span class="na"&gt;align&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;center&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;&lt;span class="cm"&gt;&amp;lt;!--#include virtual=&amp;quot;html.ng/site=spacejam&amp;amp;type=movie&amp;amp;home=no&amp;amp;size=234&amp;quot;--&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;td&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;tr&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;table&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;p&gt;Okay, let&amp;#8217;s check out &lt;a href="https://www.spacejam.com/1996/cmp/jamcentral/jamcentralframes.html"&gt;jam central&lt;/a&gt;.  I&amp;#8217;ve used my browser dev tools to reduce the viewport to 640×480 for the authentic experience (although I&amp;#8217;d also have lost some vertical space to the title bar, taskbar, and five or six &lt;span class="caps"&gt;IE&lt;/span&gt;&amp;nbsp;toolbars).&lt;/p&gt;
&lt;p&gt;Note the frames: the logo in the top left leads back to the landing page, cleverly saving screen space on repeating all that navigation, and the top right is a fucking ad banner which has been blocked like seven different ways.  All three parts are separate&amp;nbsp;pages.&lt;/p&gt;
&lt;div class="prose-full-illustration"&gt;
&lt;img src="https://eev.ee/media/2020-02-css/space-jam-central.png" alt="Screenshot of the Space Jam website's 'Jam Central'"&gt;
&lt;/div&gt;

&lt;p&gt;Note also the utterly unreadable red text on a textured background, one of the truest hallmarks of 90s web design.  &amp;#8220;Why not put that block of text on an easier-to-read background?&amp;#8221; you might ask.  You imbecile.  How would I &lt;em&gt;possibly&lt;/em&gt; do that?  Only the &lt;code&gt;&amp;lt;body&amp;gt;&lt;/code&gt; has a &lt;code&gt;background&lt;/code&gt; attribute!  I could use a table, but tables only support solid background colors, and that would look so&amp;nbsp;boring!&lt;/p&gt;
&lt;p&gt;But wait, what is this new navigation widget?  How are the links all misaligned like that?  Is this yet another table?  Well, no, although filling a table with chunks of a sliced-up image wasn&amp;#8217;t uncommon.  But this is an &lt;em&gt;imagemap&lt;/em&gt;, a long-forgotten &lt;span class="caps"&gt;HTML&lt;/span&gt; feature.  I&amp;#8217;ll just show you the&amp;nbsp;source:&lt;/p&gt;
&lt;table class="highlighttable"&gt;&lt;tr&gt;&lt;td class="linenos"&gt;&lt;div class="linenodiv"&gt;&lt;pre&gt;&lt;span class="normal"&gt;1&lt;/span&gt;
&lt;span class="normal"&gt;2&lt;/span&gt;
&lt;span class="normal"&gt;3&lt;/span&gt;
&lt;span class="normal"&gt;4&lt;/span&gt;
&lt;span class="normal"&gt;5&lt;/span&gt;
&lt;span class="normal"&gt;6&lt;/span&gt;
&lt;span class="normal"&gt;7&lt;/span&gt;
&lt;span class="normal"&gt;8&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/td&gt;&lt;td class="code"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;img&lt;/span&gt; &lt;span class="na"&gt;src&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;img/m-central.jpg&amp;quot;&lt;/span&gt; &lt;span class="na"&gt;height&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;301&lt;/span&gt; &lt;span class="na"&gt;width&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;438&lt;/span&gt; &lt;span class="na"&gt;border&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;0&lt;/span&gt; &lt;span class="na"&gt;alt&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;navigation map&amp;quot;&lt;/span&gt; &lt;span class="na"&gt;usemap&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;#map&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;br&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;

&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;map&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;map&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;area&lt;/span&gt; &lt;span class="na"&gt;shape&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;rect&amp;quot;&lt;/span&gt; &lt;span class="na"&gt;coords&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;33,92,178,136&amp;quot;&lt;/span&gt; &lt;span class="na"&gt;href&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;prodnotesframes.html&amp;quot;&lt;/span&gt; &lt;span class="na"&gt;target&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;_top&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;area&lt;/span&gt; &lt;span class="na"&gt;shape&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;rect&amp;quot;&lt;/span&gt; &lt;span class="na"&gt;coords&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;244,111,416,152&amp;quot;&lt;/span&gt; &lt;span class="na"&gt;href&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;photosframes.html&amp;quot;&lt;/span&gt; &lt;span class="na"&gt;target&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;_top&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;area&lt;/span&gt; &lt;span class="na"&gt;shape&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;rect&amp;quot;&lt;/span&gt; &lt;span class="na"&gt;coords&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;104,138,229,181&amp;quot;&lt;/span&gt; &lt;span class="na"&gt;href&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;filmmakersframes.html&amp;quot;&lt;/span&gt; &lt;span class="na"&gt;target&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;_top&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;area&lt;/span&gt; &lt;span class="na"&gt;shape&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;rect&amp;quot;&lt;/span&gt; &lt;span class="na"&gt;coords&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;230,155,334,197&amp;quot;&lt;/span&gt; &lt;span class="na"&gt;href&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;trailerframes.html&amp;quot;&lt;/span&gt; &lt;span class="na"&gt;target&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;_top&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;map&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;p&gt;I assume this is more or less self-explanatory.  The &lt;code&gt;usemap&lt;/code&gt; attribute attaches an image map, which is defined as a bunch of clickable areas, beautifully encoded as inscrutable lists of coordinates or&amp;nbsp;something.&lt;/p&gt;
&lt;p&gt;And this stuff still works!  This is in &lt;span class="caps"&gt;HTML&lt;/span&gt;!  You could use it right now!  Probably don&amp;#8217;t&amp;nbsp;though!&lt;/p&gt;
&lt;h3 id="the-thumbnail-grid"&gt;&lt;a class="toclink" href="#the-thumbnail-grid"&gt;The thumbnail&amp;nbsp;grid&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Let&amp;#8217;s look at one more random page here.  I&amp;#8217;d love to see some photos from the film.  (Wait, &lt;em&gt;photos&lt;/em&gt;?  Did we not know what &amp;#8220;screenshots&amp;#8221; were&amp;nbsp;yet?)&lt;/p&gt;
&lt;div class="prose-full-illustration"&gt;
&lt;img src="https://eev.ee/media/2020-02-css/space-jam-photos.png" alt="Screenshot of the Space Jam website's photos page"&gt;
&lt;/div&gt;

&lt;p&gt;Another frameset, but arranged differently this&amp;nbsp;time.&lt;/p&gt;
&lt;table class="highlighttable"&gt;&lt;tr&gt;&lt;td class="linenos"&gt;&lt;div class="linenodiv"&gt;&lt;pre&gt;&lt;span class="normal"&gt;1&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/td&gt;&lt;td class="code"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;body&lt;/span&gt; &lt;span class="na"&gt;bgcolor&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;#7714bf&amp;quot;&lt;/span&gt; &lt;span class="na"&gt;background&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;img/bg-jamcentral.gif&amp;quot;&lt;/span&gt; &lt;span class="na"&gt;text&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;#ffffff&amp;quot;&lt;/span&gt; &lt;span class="na"&gt;link&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;#edb2fc&amp;quot;&lt;/span&gt; &lt;span class="na"&gt;vlink&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;#edb2fc&amp;quot;&lt;/span&gt; &lt;span class="na"&gt;alink&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;#edb2fc&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;p&gt;They did an important thing here: since they specified a background image (which is opaque), they &lt;em&gt;also&lt;/em&gt; specified a background color.  Without it, if the background image failed to load, the page would be white text on the default white background, which would be&amp;nbsp;unreadable.&lt;/p&gt;
&lt;p&gt;(That&amp;#8217;s &lt;em&gt;still&lt;/em&gt; an important thing to keep in mind.  I feel like modern web development tends to assume everything will load, or sees loading as some sort of inconvenience to be worked around, but not everyone is working on a wired connection in a San Francisco office twenty feet away from a&amp;nbsp;backbone.)&lt;/p&gt;
&lt;p&gt;But about the page itself.  Thumbnail grids are a classic problem of web design, dating all the way back to&amp;#8230;  er&amp;#8230;  well, at least as far back as Space Jam.  The main issue is that you want to &lt;em&gt;put things next to each other&lt;/em&gt;, whereas &lt;span class="caps"&gt;HTML&lt;/span&gt; defaults to stacking everything in one big column.  You could put all the thumbnails inline, in a single row of (wrapping) text, but that wouldn&amp;#8217;t be much of a grid — and you usually want each one to have some sort of&amp;nbsp;caption.&lt;/p&gt;
&lt;p&gt;Space Jam&amp;#8217;s approach was to use the only real tool anyone had in their toolbox at the time: a table.  It&amp;#8217;s structured like&amp;nbsp;this:&lt;/p&gt;
&lt;table class="highlighttable"&gt;&lt;tr&gt;&lt;td class="linenos"&gt;&lt;div class="linenodiv"&gt;&lt;pre&gt;&lt;span class="normal"&gt;1&lt;/span&gt;
&lt;span class="normal"&gt;2&lt;/span&gt;
&lt;span class="normal"&gt;3&lt;/span&gt;
&lt;span class="normal"&gt;4&lt;/span&gt;
&lt;span class="normal"&gt;5&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/td&gt;&lt;td class="code"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;table&lt;/span&gt; &lt;span class="na"&gt;cellpadding&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;10&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;tr&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;td&lt;/span&gt; &lt;span class="na"&gt;align&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;center&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;a&lt;/span&gt; &lt;span class="na"&gt;href&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;...&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;img&lt;/span&gt; &lt;span class="na"&gt;src&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;...&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;a&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;td&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;...&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;tr&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;tr&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;...&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;tr&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;tr&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;...&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;tr&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;table&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;p&gt;A 3×3 grid of thumbnails, left to the browser to arrange.  (The last image, on a row of its own, isn&amp;#8217;t actually part of the table.)  This can&amp;#8217;t scale to fit your screen, but everyone&amp;#8217;s screen was pretty tiny back then, so that was &lt;em&gt;slightly&lt;/em&gt; less of a concern.  They didn&amp;#8217;t add captions here, but since every thumbnail is wrapped in a table cell, they easily could&amp;nbsp;have.&lt;/p&gt;
&lt;p&gt;This was the state of the art in thumbnail grids in 1996.  We&amp;#8217;ll be revisiting this little &lt;span class="caps"&gt;UI&lt;/span&gt; puzzle a few times; you can see live examples (and view source for sample markup) on a &lt;a href="https://eev.ee/media/2020-02-css/thumbnail-grids.html#tables"&gt;separate page&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;But let&amp;#8217;s take a moment to appreciate the size of the &amp;#8220;full-size, full-color, internet-quality&amp;#8221; movie screenshots on my current&amp;nbsp;monitor.&lt;/p&gt;
&lt;div class="prose-full-illustration"&gt;
&lt;img src="https://eev.ee/media/2020-02-css/space-jam-photo-size.png" alt="Screenshot of one of the Space Jam website's full-size photos, fullscreened on my monitor"&gt;
&lt;/div&gt;

&lt;p&gt;Hey, though, they&amp;#8217;re less than 16 &lt;span class="caps"&gt;KB&lt;/span&gt;!  That&amp;#8217;ll only take nine seconds to&amp;nbsp;download.&lt;/p&gt;
&lt;p&gt;(I&amp;#8217;m reminded of the problem of embedded &lt;em&gt;video&lt;/em&gt;, which wasn&amp;#8217;t solved until &lt;span class="caps"&gt;HTML5&lt;/span&gt;&amp;#8217;s &lt;code&gt;&amp;lt;video&amp;gt;&lt;/code&gt; tag some years later.  Until then, you had to use a binary plugin, and all of them were&amp;nbsp;terrible.)&lt;/p&gt;
&lt;p&gt;(Oh, by the way: images within links, by default, have a link-colored border around them.  Image links are &lt;em&gt;usually&lt;/em&gt; self-evident, so this was largely annoying, and until &lt;span class="caps"&gt;CSS&lt;/span&gt; you had to disable them for every single image with &lt;code&gt;&amp;lt;img border=0&amp;gt;&lt;/code&gt;.)&lt;/p&gt;
&lt;h2 id="the-regular-early-days"&gt;&lt;a class="toclink" href="#the-regular-early-days"&gt;The regular early&amp;nbsp;days&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;So that&amp;#8217;s where we started, and it sucked.  If you wanted &lt;em&gt;any&lt;/em&gt; kind of consistency on more than a handful of pages, your options were very limited, and they were pretty much limited to a whole lot of copying and pasting.  The Space Jam website opted to, for the most part, not bother at all — as did many&amp;nbsp;others.&lt;/p&gt;
&lt;p&gt;Then &lt;span class="caps"&gt;CSS&lt;/span&gt; came along, it was a &lt;em&gt;fucking miracle&lt;/em&gt;.  All that inline repetition went away.  You want all your top-level headings to be a particular color?  No&amp;nbsp;problem:&lt;/p&gt;
&lt;table class="highlighttable"&gt;&lt;tr&gt;&lt;td class="linenos"&gt;&lt;div class="linenodiv"&gt;&lt;pre&gt;&lt;span class="normal"&gt;1&lt;/span&gt;
&lt;span class="normal"&gt;2&lt;/span&gt;
&lt;span class="normal"&gt;3&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/td&gt;&lt;td class="code"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="nt"&gt;H1&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;color&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mh"&gt;#FF0000&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;p&gt;Bam!  You&amp;#8217;re done.  No matter how many &lt;code&gt;&amp;lt;h1&amp;gt;&lt;/code&gt;s you have in your document, every single one of them will be eye-searing red, and you never have to think about it again.  Even better, you can put that snippet in its own file and have that questionable aesthetic choice applied to &lt;em&gt;every page of your whole site&lt;/em&gt; with almost no effort!  The same applied to your gorgeous tiling background image, the colors of your links, and the size of the font in your&amp;nbsp;tables.&lt;/p&gt;
&lt;p&gt;(Just remember to wrap the contents of your &lt;code&gt;&amp;lt;style&amp;gt;&lt;/code&gt; tags in &lt;span class="caps"&gt;HTML&lt;/span&gt; comments, or old browsers without &lt;span class="caps"&gt;CSS&lt;/span&gt; support will display them as&amp;nbsp;text.)&lt;/p&gt;
&lt;p&gt;You weren&amp;#8217;t limited to styling tags en masse, either.  &lt;span class="caps"&gt;CSS&lt;/span&gt; introduced &amp;#8220;classes&amp;#8221; and &amp;#8220;IDs&amp;#8221; to target only specifically flagged elements.  A &lt;em&gt;selector&lt;/em&gt; like &lt;code&gt;P.important&lt;/code&gt; would only affect &lt;code&gt;&amp;lt;P CLASS="important"&amp;gt;&lt;/code&gt;, and &lt;code&gt;#header&lt;/code&gt; would only affect &lt;code&gt;&amp;lt;H1 ID="header"&amp;gt;&lt;/code&gt;.  (The difference is that IDs are intended to be unique in a document, whereas classes can be used any number of times.)  With these tools, you could effectively invent your own tags, giving you a customized version of &lt;span class="caps"&gt;HTML&lt;/span&gt; specific to your&amp;nbsp;website!&lt;/p&gt;
&lt;p&gt;This was a huge leap forward, but at the time, no one (probably?) was thinking of using &lt;span class="caps"&gt;CSS&lt;/span&gt; to actually &lt;em&gt;arrange&lt;/em&gt; the page.  When &lt;a href="https://www.w3.org/TR/2008/REC-CSS1-20080411/"&gt;&lt;span class="caps"&gt;CSS&lt;/span&gt; 1&lt;/a&gt; was made a recommendation in December &amp;#8216;96, it barely addressed layout at all.  All it did was divorce &lt;span class="caps"&gt;HTML&lt;/span&gt;&amp;#8217;s &lt;em&gt;existing&lt;/em&gt; abilities from the tags they were attached to.  We had font colors and backgrounds &lt;em&gt;because&lt;/em&gt; &lt;code&gt;&amp;lt;FONT COLOR&amp;gt;&lt;/code&gt; and &lt;code&gt;&amp;lt;BODY BACKGROUND&amp;gt;&lt;/code&gt; existed.  The only feature that even remotely affected where things were positioned was the &lt;code&gt;float&lt;/code&gt; property, the equivalent to &lt;code&gt;&amp;lt;IMG ALIGN&amp;gt;&lt;/code&gt;, which pulled an image to the side and let text flow around it, like in a magazine article.  Hardly&amp;nbsp;whelming.&lt;/p&gt;
&lt;p&gt;This wasn&amp;#8217;t too surprising.  &lt;span class="caps"&gt;HTML&lt;/span&gt; hadn&amp;#8217;t had any real answers for layout besides tables, and the table properties were too complicated to generalize in &lt;span class="caps"&gt;CSS&lt;/span&gt; and too entangled with the tag structure, so there was nothing for &lt;span class="caps"&gt;CSS&lt;/span&gt; 1 to inherit.  It merely reduced the repetition in what we were already doing with e.g. &lt;code&gt;&amp;lt;FONT&amp;gt;&lt;/code&gt; tags — making Web design less tedious, less error-prone, less full of noise, and much more maintainable.  A pretty good step forward, and everyone happily adopted it for that, but tables remained king for arranging your&amp;nbsp;page.&lt;/p&gt;
&lt;p&gt;That was okay, though; all your blog really needed was a header and a sidebar, which tables could do just fine, and it wasn&amp;#8217;t like you were going to overhaul that basic structure very often.  Copy/pasting a few lines of &lt;code&gt;&amp;lt;TABLE BORDER=0&amp;gt;&lt;/code&gt; and &lt;code&gt;&amp;lt;TD WIDTH=20%&amp;gt;&lt;/code&gt; wasn&amp;#8217;t nearly as big a&amp;nbsp;deal.&lt;/p&gt;
&lt;p&gt;For some span of time — I want to say a couple years, but time passes more slowly when you&amp;#8217;re a kid — this was the state of the Web.  Tables for layout, &lt;span class="caps"&gt;CSS&lt;/span&gt; for&amp;#8230;  well, &lt;em&gt;style&lt;/em&gt;.  Colors, sizes, bold, underline.  There was even this sick trick you could do with links where they&amp;#8217;d &lt;em&gt;only&lt;/em&gt; be underlined when the mouse was &lt;em&gt;pointing&lt;/em&gt; at them.&amp;nbsp;Tubular!&lt;/p&gt;
&lt;p&gt;(Fun fact: &lt;span class="caps"&gt;HTML&lt;/span&gt; &lt;em&gt;email&lt;/em&gt; is still basically trapped in this&amp;nbsp;era.)&lt;/p&gt;
&lt;p&gt;(And here&amp;#8217;s about where I come in, at the ripe old age of 11, with no clue what I was doing and mostly learning from other 11-year-olds who also had no clue what they were doing.  But that was fine; a huge chunk of the Web was 11-year-olds making their own websites, and it was beautiful.  Why would you go to a &lt;em&gt;business&lt;/em&gt; website when you can take a peek into the very specific hobbies of someone on the other side of the&amp;nbsp;planet?)&lt;/p&gt;
&lt;h2 id="the-dark-times"&gt;&lt;a class="toclink" href="#the-dark-times"&gt;The dark&amp;nbsp;times&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;A year and a half later, in mid &amp;#8216;98, we were gifted &lt;a href="https://www.w3.org/TR/2008/REC-CSS2-20080411/"&gt;&lt;span class="caps"&gt;CSS&lt;/span&gt; 2&lt;/a&gt;.  (I &lt;em&gt;love&lt;/em&gt; the background on this page, by the way.)  This was a modest upgrade that addressed a few deficiencies in various areas, but most interesting was the addition of a couple positioning primitives: the &lt;code&gt;position&lt;/code&gt; property, which let you place elements at precise coordinates, and the &lt;code&gt;inline-block&lt;/code&gt; display mode, which let you stick an element in a line of text like you could do with&amp;nbsp;images.&lt;/p&gt;
&lt;p&gt;Such tantalizing fruit, just out of reach!  Using &lt;code&gt;position&lt;/code&gt; seemed nice, but pixel-perfect positioning was at serious odds with the fluid design of &lt;span class="caps"&gt;HTML&lt;/span&gt;, and it was difficult to make much of anything that didn&amp;#8217;t fall apart on other screen sizes or have other serious drawbacks.  This humble &lt;code&gt;inline-block&lt;/code&gt; thing &lt;em&gt;seemed&lt;/em&gt; interesting enough; after all, it solved the core problem of &lt;span class="caps"&gt;HTML&lt;/span&gt; layout, which is &lt;em&gt;putting things next to each other&lt;/em&gt;.  But at least for the moment, no browser implemented it, and it was largely&amp;nbsp;ignored.&lt;/p&gt;
&lt;p&gt;I can&amp;#8217;t say for sure if it was the introduction of positioning or some other factor, but &lt;em&gt;something&lt;/em&gt; around this time inspired folks to try doing layout in &lt;span class="caps"&gt;CSS&lt;/span&gt;.  Ideally, you would &lt;em&gt;completely&lt;/em&gt; divorce the structure of your page from its appearance.  A website even came along to take this principle to the extreme — &lt;a href="http://www.csszengarden.com/"&gt;&lt;span class="caps"&gt;CSS&lt;/span&gt; Zen Garden&lt;/a&gt; is still around, and showcases the &lt;em&gt;same &lt;span class="caps"&gt;HTML&lt;/span&gt;&lt;/em&gt; being radically transformed into completely different designs by applying different&amp;nbsp;stylesheets.&lt;/p&gt;
&lt;p&gt;Trouble was, early &lt;span class="caps"&gt;CSS&lt;/span&gt; support was buggy as hell.  In retrospect, I suspect browser vendors merely plucked the behavior off of &lt;span class="caps"&gt;HTML&lt;/span&gt; tags and called it a day.  I&amp;#8217;m delighted to say that RichInStyle still has &lt;a href="http://www.richinstyle.com/bugs/"&gt;an extensive list of early browser &lt;span class="caps"&gt;CSS&lt;/span&gt; bugs&lt;/a&gt; up; here are some of my&amp;nbsp;favorites:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;span class="caps"&gt;IE&lt;/span&gt; 3 would ignore all but the last &lt;code&gt;&amp;lt;style&amp;gt;&lt;/code&gt; tag in a&amp;nbsp;document.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;span class="caps"&gt;IE&lt;/span&gt; 3 ignored pseudo-classes, so &lt;code&gt;a:hover&lt;/code&gt; would be treated as &lt;code&gt;a&lt;/code&gt;.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;span class="caps"&gt;IE&lt;/span&gt; 3 and &lt;span class="caps"&gt;IE&lt;/span&gt; 4 treated &lt;code&gt;auto&lt;/code&gt; margins as zero.  Actually, I think this one might&amp;#8217;ve persisted all the way to &lt;span class="caps"&gt;IE&lt;/span&gt; 6.  But that was okay, because &lt;span class="caps"&gt;IE&lt;/span&gt; 6 also incorrectly applied &lt;code&gt;text-align: center&lt;/code&gt; to block&amp;nbsp;elements.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;If you set a background image to an absolute &lt;span class="caps"&gt;URL&lt;/span&gt;, &lt;span class="caps"&gt;IE&lt;/span&gt; 3 would try to open the image in a local program, as though you&amp;#8217;d downloaded&amp;nbsp;it.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Netscape 4 understood an &lt;span class="caps"&gt;ID&lt;/span&gt; selector like &lt;code&gt;#id&lt;/code&gt;, but ignored &lt;code&gt;h1#id&lt;/code&gt; as&amp;nbsp;invalid.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Netscape 4 didn&amp;#8217;t inherit properties — including font and text color! — into table&amp;nbsp;cells.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Netscape 4 applied properties on &lt;code&gt;&amp;lt;li&amp;gt;&lt;/code&gt; to the list &lt;em&gt;marker&lt;/em&gt;, rather than the&amp;nbsp;contents.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;If the same element has both &lt;code&gt;float&lt;/code&gt; and &lt;code&gt;clear&lt;/code&gt; (not unreasonable), Netscape 4 for Mac&amp;nbsp;crashes.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;This is what we had to work with.  And folks wanted to use &lt;span class="caps"&gt;CSS&lt;/span&gt; to &lt;em&gt;lay out&lt;/em&gt; an &lt;em&gt;entire page&lt;/em&gt;?&amp;nbsp;Ha.&lt;/p&gt;
&lt;p&gt;Yet the idea grew in popularity.  It even became a sort of elitist rallying cry, a best practice used to beat other folks over the head.  Tables for layout are just plain bad, you&amp;#8217;d hear!  They confuse screenreaders, they&amp;#8217;re semantically incorrect, they interact poorly with &lt;span class="caps"&gt;CSS&lt;/span&gt; positioning!  All of which is true, but it was a much tougher pill to swallow when the alternative&amp;nbsp;was—&lt;/p&gt;
&lt;p&gt;Well, we&amp;#8217;ll get to that in a moment.  First, some background on the Web landscape circa&amp;nbsp;2000.&lt;/p&gt;
&lt;h3 id="the-end-of-the-browser-wars-and-subsequent-stagnation"&gt;&lt;a class="toclink" href="#the-end-of-the-browser-wars-and-subsequent-stagnation"&gt;The end of the browser wars and subsequent&amp;nbsp;stagnation&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;The short version is: this company Netscape had been selling its Navigator browser (to businesses; it was free for personal use), and then Microsoft entered the market with its completely free Internet Explorer browser, and &lt;em&gt;then&lt;/em&gt; Microsoft had the audacity to bundle &lt;span class="caps"&gt;IE&lt;/span&gt; with Windows.  Can you imagine?  An operating system that &lt;em&gt;comes with&lt;/em&gt; a browser?  This was a whole big thing, &lt;a href="https://en.wikipedia.org/wiki/United_States_v._Microsoft_Corp."&gt;Microsoft was sued over it&lt;/a&gt;, and they lost, and the consequence was basically&amp;nbsp;nothing.&lt;/p&gt;
&lt;p&gt;But it wouldn&amp;#8217;t have mattered either way, because they&amp;#8217;d still &lt;em&gt;done it&lt;/em&gt;, and it had worked.  &lt;span class="caps"&gt;IE&lt;/span&gt; pretty much annihilated Netscape&amp;#8217;s market share.  Both browsers were buggy as hell, and &lt;em&gt;differently&lt;/em&gt; buggy as hell, so a site built exclusively against one was likely to be a big mess when viewed in the other — this meant that when Netscape&amp;#8217;s market share dropped, web designers paid less and less attention to it, and less of the Web worked in it, and its market share dropped&amp;nbsp;further.&lt;/p&gt;
&lt;p&gt;Sucks for you if you don&amp;#8217;t use Windows, I guess.  Which is funny, because there was an &lt;span class="caps"&gt;IE&lt;/span&gt; for Mac 5.5, and it was generally &lt;em&gt;less&lt;/em&gt; buggy than &lt;span class="caps"&gt;IE&lt;/span&gt; 6.  (Incidentally, Bill Gates wasn&amp;#8217;t so much a brilliant nerd as an aggressive and ruthless businessman who made his fortune by deliberately striving to annihilate any competition standing in his way and making computing worse overall as a result, just&amp;nbsp;saying.)&lt;/p&gt;
&lt;p&gt;By the time Windows &lt;span class="caps"&gt;XP&lt;/span&gt; shipped in mid 2001, with Internet Explorer 6 built in, Netscape had gone from a juggernaut to a tiny niche&amp;nbsp;player.&lt;/p&gt;
&lt;p&gt;And then, having completely and utterly dominated, Microsoft stopped.  Internet Explorer had seen a release every year or so since its inception, but &lt;span class="caps"&gt;IE&lt;/span&gt; 6 was the last release for more than five years.  It was still buggy, but that was less noticeable when there was no competition, and it was &lt;em&gt;good enough&lt;/em&gt;.  Windows &lt;span class="caps"&gt;XP&lt;/span&gt;, likewise, was good enough to take over the desktop, and there wouldn&amp;#8217;t be another Windows for just as&amp;nbsp;long.&lt;/p&gt;
&lt;p&gt;The &lt;span class="caps"&gt;W3C&lt;/span&gt;, the group who write the standards (not to be confused with W3Schools, who are shady &lt;span class="caps"&gt;SEO&lt;/span&gt; leeches), also stopped.  &lt;span class="caps"&gt;HTML&lt;/span&gt; had seen several revisions throughout the mid 90s, and then froze as &lt;span class="caps"&gt;HTML&lt;/span&gt; 4.  &lt;span class="caps"&gt;CSS&lt;/span&gt; had gotten an update in only a year and a half, and then no more; the minor update &lt;a href="https://www.w3.org/TR/CSS21/"&gt;&lt;span class="caps"&gt;CSS&lt;/span&gt; 2.1&lt;/a&gt; wouldn&amp;#8217;t hit Candidate Recommendation status until early 2004, and took another seven years to be&amp;nbsp;finalized.&lt;/p&gt;
&lt;p&gt;With &lt;span class="caps"&gt;IE&lt;/span&gt; 6&amp;#8217;s dominance, it was as if the entire Web was frozen in time.  Standards didn&amp;#8217;t matter, because there was effectively only one browser, and whatever it did became the de facto standard.  As the Web grew in popularity, &lt;span class="caps"&gt;IE&lt;/span&gt;&amp;#8217;s stranglehold also made it difficult to use any platform other than Windows, since &lt;span class="caps"&gt;IE&lt;/span&gt; was Windows-only and it was a coin flip whether a website would actually work with any other&amp;nbsp;browser.&lt;/p&gt;
&lt;p&gt;(One begins to suspect that monopolies are bad.  There oughta be a&amp;nbsp;law!)&lt;/p&gt;
&lt;p&gt;In the meantime, Netscape had put themselves in an even worse position by deciding to do a massive rewrite of their browser engine, culminating in the vastly more standards-compliant Netscape 6 — at the cost of several years away from the market while &lt;span class="caps"&gt;IE&lt;/span&gt; was kicking their ass.  It never broke 10% market share, while &lt;span class="caps"&gt;IE&lt;/span&gt;&amp;#8217;s would peak at 96%.  On the other hand, the new engine was open sourced as the Mozilla Application Suite, which would be important in a few&amp;nbsp;years.&lt;/p&gt;
&lt;p&gt;Before we get to that, some other things were also&amp;nbsp;happening.&lt;/p&gt;
&lt;h3 id="quirks-mode"&gt;&lt;a class="toclink" href="#quirks-mode"&gt;Quirks&amp;nbsp;mode&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;All early &lt;span class="caps"&gt;CSS&lt;/span&gt; implementations were riddled with bugs, but one in particular is perhaps the most infamous &lt;span class="caps"&gt;CSS&lt;/span&gt; bug of all time: the &lt;em&gt;box model bug&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;You see, a box (the rectangular space taken up by an element) has several measurements: its own width and height, then surrounding whitespace called padding, then an optional border, then a margin separating it from neighboring boxes.  &lt;span class="caps"&gt;CSS&lt;/span&gt; specifies that these properties are all additive.  A box with these&amp;nbsp;styles:&lt;/p&gt;
&lt;table class="highlighttable"&gt;&lt;tr&gt;&lt;td class="linenos"&gt;&lt;div class="linenodiv"&gt;&lt;pre&gt;&lt;span class="normal"&gt;1&lt;/span&gt;
&lt;span class="normal"&gt;2&lt;/span&gt;
&lt;span class="normal"&gt;3&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/td&gt;&lt;td class="code"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nt"&gt;width&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;100px&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nt"&gt;padding&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;10px&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nt"&gt;border&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;2px&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;solid&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;black&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;p&gt;&amp;#8230;would thus be 124 pixels wide, from border to&amp;nbsp;border.&lt;/p&gt;
&lt;p&gt;&lt;span class="caps"&gt;IE&lt;/span&gt; 4 and Netscape 4, on the other hand, took a different approach: they treated &lt;code&gt;width&lt;/code&gt; and &lt;code&gt;height&lt;/code&gt; as measuring from border to border, and they &lt;em&gt;subtracted&lt;/em&gt; the border and padding to get the width of the element itself.  The same box in those browsers would be 100 pixels wide from border to border, with 76 pixels remaining for the&amp;nbsp;content.&lt;/p&gt;
&lt;p&gt;This conflict with the spec was not ideal, and &lt;span class="caps"&gt;IE&lt;/span&gt; 6 set out to fix it.  Unfortunately, simply making the change would mean completely breaking the design of a whole lot of websites that had previously worked in &lt;em&gt;both&lt;/em&gt; &lt;span class="caps"&gt;IE&lt;/span&gt; and&amp;nbsp;Netscape.&lt;/p&gt;
&lt;p&gt;So the &lt;span class="caps"&gt;IE&lt;/span&gt; team came up with a very strange compromise: they declared the old behavior (along with several other major bugs) as &amp;#8220;quirks mode&amp;#8221; and made it the &lt;em&gt;default&lt;/em&gt;.  The new &amp;#8220;strict mode&amp;#8221; or &amp;#8220;standards mode&amp;#8221; had to be opted &lt;em&gt;into&lt;/em&gt;, by placing a &amp;#8220;doctype&amp;#8221; at the beginning of your document, before the &lt;code&gt;&amp;lt;html&amp;gt;&lt;/code&gt; tag.  It would look something like&amp;nbsp;this:&lt;/p&gt;
&lt;table class="highlighttable"&gt;&lt;tr&gt;&lt;td class="linenos"&gt;&lt;div class="linenodiv"&gt;&lt;pre&gt;&lt;span class="normal"&gt;1&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/td&gt;&lt;td class="code"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="cp"&gt;&amp;lt;!DOCTYPE html PUBLIC &amp;quot;-//W3C//DTD HTML 4.01//EN&amp;quot; &amp;quot;http://www.w3.org/TR/html4/strict.dtd&amp;quot;&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;p&gt;Everyone had to paste this damn mess of a line at the top of every single &lt;span class="caps"&gt;HTML&lt;/span&gt; document for years.  (&lt;span class="caps"&gt;HTML5&lt;/span&gt; would later simplify it to &lt;code&gt;&amp;lt;!DOCTYPE html&amp;gt;&lt;/code&gt;.)  In retrospect, it&amp;#8217;s a really strange way to opt into correct &lt;span class="caps"&gt;CSS&lt;/span&gt; behavior; doctypes had been part of the &lt;span class="caps"&gt;HTML&lt;/span&gt; spec since way back when it was an &lt;a href="https://tools.ietf.org/html/rfc1866"&gt;&lt;span class="caps"&gt;RFC&lt;/span&gt;&lt;/a&gt;.  I&amp;#8217;m guessing the idea was that, since &lt;em&gt;nobody&lt;/em&gt; bothered actually including one, it was a convenient way to allow opting in without requiring proprietary extensions just to avoid behavior that had been wrong in the first place.  Good for the &lt;span class="caps"&gt;IE&lt;/span&gt;&amp;nbsp;team!&lt;/p&gt;
&lt;p&gt;The funny thing is, quirks mode still exists &lt;em&gt;and is still the default&lt;/em&gt; in all browsers, twenty years later!  The exact quirks have varied over time, and in particular neither Chrome nor Firefox use the &lt;span class="caps"&gt;IE&lt;/span&gt; box model even in quirks mode, but there are still &lt;a href="https://developer.mozilla.org/en-US/docs/Mozilla/Mozilla_quirks_mode_behavior"&gt;quite a few other emulated bugs&lt;/a&gt;.&lt;/p&gt;
&lt;aside class="aside--note-from-future"&gt;
&lt;p&gt;Hello!  Eevee here, almost two years later.  You may notice the preceding link is broken.  Well, it seems Mozilla made the completely baffling decision to &lt;a href="https://groups.google.com/a/mozilla.org/g/dev-platform/c/HwRoRUOuyEw/m/ZfYG7oHZDQAJ?pli=1"&gt;nuke all Mozilla-specific information from MDN&lt;/a&gt; on the grounds that it really belongs in Firefox documentation, then failed to add it to the Firefox documentation.  So some critical technical information that's also of deep historical interest, like exactly what quirks mode even &lt;em&gt;does&lt;/em&gt; in Firefox, is now lost, except for the unreadable &lt;a href="https://github.com/mdn/archived-content/blob/main/files/en-us/mozilla/mozilla_quirks_mode_behavior/index.html"&gt;archived copy&lt;/a&gt;.  This also reduces the only mention of quirks mode on MDN to &lt;a href="https://developer.mozilla.org/en-US/docs/Web/HTML/Quirks_Mode_and_Standards_Mode"&gt;this lone article&lt;/a&gt;, which says very vaguely what it is but doesn't offer so much as a glimpse at what the differences actually are.  What a fucking&amp;nbsp;circus.&lt;/p&gt;
&lt;/aside&gt;
&lt;p&gt;Modern browsers also have &amp;#8220;almost standards&amp;#8221; mode, which emulates only a single quirk, perhaps the second most infamous one: if a table cell contains only a single image, the space under the baseline is removed.  Under normal &lt;span class="caps"&gt;CSS&lt;/span&gt; rules, the image is sitting within a line of (otherwise empty) text, which requires some space reserved underneath for descenders — the tails on letters like y.  Early browsers didn&amp;#8217;t handle this correctly, and some otherwise strict-mode websites from circa 2000 rely on it — e.g., by cutting up a large image and arranging the chunks in table cells, expecting them to display flush against each other — hence the intermediate mode to keep them limping&amp;nbsp;along.&lt;/p&gt;
&lt;p&gt;But getting back to the past: while this was certainly a win for standards (and thus interop), it created a new problem.  Since &lt;span class="caps"&gt;IE&lt;/span&gt; 6 dominated, and doctypes were optional, there was little compelling reason to bother with strict mode.  Other browsers ended up &lt;em&gt;emulating&lt;/em&gt; it, and the non-standard behavior became its own de facto standard.  Web designers who cared about this sort of thing (and to our credit, there were a lot of us) made a rallying cry out of enabling strict mode, since it was the absolute barest minimum step towards ensuring compatibility with other&amp;nbsp;browsers.&lt;/p&gt;
&lt;h3 id="the-rise-and-fall-of-xhtml"&gt;&lt;a class="toclink" href="#the-rise-and-fall-of-xhtml"&gt;The rise and fall of&amp;nbsp;XHTML&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Meanwhile, the &lt;span class="caps"&gt;W3C&lt;/span&gt; had lost interest in &lt;span class="caps"&gt;HTML&lt;/span&gt; in favor of developing &lt;span class="caps"&gt;XHTML&lt;/span&gt;, an attempt to redesign &lt;span class="caps"&gt;HTML&lt;/span&gt; with the syntax of &lt;span class="caps"&gt;XML&lt;/span&gt; rather than &lt;span class="caps"&gt;SGML&lt;/span&gt;.&lt;/p&gt;
&lt;p&gt;(What on Earth is &lt;span class="caps"&gt;SGML&lt;/span&gt;, you ask?  I don&amp;#8217;t know.  Nobody knows.  It&amp;#8217;s the grammar &lt;span class="caps"&gt;HTML&lt;/span&gt; was built on, and that&amp;#8217;s the only reason anyone has heard of&amp;nbsp;it.)&lt;/p&gt;
&lt;p&gt;To their credit, there were some good reasons to do this at the time.  &lt;span class="caps"&gt;HTML&lt;/span&gt; was generally hand-written (as it still is now), and anything hand-written is likely to have the occasional bugs.  Browsers weren&amp;#8217;t in the habit of rejecting buggy &lt;span class="caps"&gt;HTML&lt;/span&gt; outright, so they had various error-correction techniques — and, as with everything else, different browsers handled errors differently.  Slightly malformed &lt;span class="caps"&gt;HTML&lt;/span&gt; might appear to work fine in &lt;span class="caps"&gt;IE&lt;/span&gt; 6 (where &amp;#8220;work fine&amp;#8221; means &amp;#8220;does what you hoped for&amp;#8221;), but turn into a horrible mess in anything&amp;nbsp;else.&lt;/p&gt;
&lt;p&gt;The &lt;span class="caps"&gt;W3C&lt;/span&gt;&amp;#8217;s solution was &lt;span class="caps"&gt;XML&lt;/span&gt;, because their solution to fucking everything in the early 2000s was &lt;span class="caps"&gt;XML&lt;/span&gt;.  If you&amp;#8217;re not aware, &lt;span class="caps"&gt;XML&lt;/span&gt; takes a much more explicit and aggressive approach to error handling — if your document contains a parse error, the &lt;em&gt;entire document&lt;/em&gt; is invalid.  That means if you bank on &lt;span class="caps"&gt;XHTML&lt;/span&gt; and make a single typo somewhere, &lt;strong&gt;nothing at all&lt;/strong&gt; renders.  Just an&amp;nbsp;error.&lt;/p&gt;
&lt;p&gt;This sucked.  It sounds okay on the face of things, but consider: generic &lt;span class="caps"&gt;XML&lt;/span&gt; is usually assembled dynamically with &lt;em&gt;libraries&lt;/em&gt; that treat a document as a tree you manipulate, then turn it all into text when you&amp;#8217;re done.  That&amp;#8217;s great for the common use of &lt;span class="caps"&gt;XML&lt;/span&gt; as data serialization, where your data is already a tree and much of the &lt;span class="caps"&gt;XML&lt;/span&gt; structure is simple and repetitive and easy to squirrel away in&amp;nbsp;functions.&lt;/p&gt;
&lt;p&gt;&lt;span class="caps"&gt;HTML&lt;/span&gt; is not like that.  An &lt;span class="caps"&gt;HTML&lt;/span&gt; document has little reliable repeating structure; even this blog post, constructed &lt;em&gt;mostly&lt;/em&gt; from &lt;code&gt;&amp;lt;p&amp;gt;&lt;/code&gt; tags, also contains surprise &lt;code&gt;&amp;lt;em&amp;gt;&lt;/code&gt;s within body text and the occasional &lt;code&gt;&amp;lt;h2&amp;gt;&lt;/code&gt; between paragraphs.  That&amp;#8217;s not fun to express as a tree.  And this is a big deal, because server-side rendering was becoming popular around the same time, and generated &lt;span class="caps"&gt;HTML&lt;/span&gt; was — still is! — put together with &lt;em&gt;templates&lt;/em&gt; that treat it as a text&amp;nbsp;stream.&lt;/p&gt;
&lt;p&gt;If &lt;span class="caps"&gt;HTML&lt;/span&gt; were only written as complete static documents, then &lt;span class="caps"&gt;XHTML&lt;/span&gt; might have worked out — you write a document, you see it in your browser, you know it works, no problem.  But generating it dynamically and risking that &lt;em&gt;particular edge cases&lt;/em&gt; might replace your entire site with an unintelligible browser error?  That&amp;nbsp;sucks.&lt;/p&gt;
&lt;p&gt;It certainly didn&amp;#8217;t help that we were just starting to hear about this newfangled Unicode thing around this time, and it was still not always clear how exactly to make that work, and one bad &lt;span class="caps"&gt;UTF&lt;/span&gt;-8 sequence is enough for an entire &lt;span class="caps"&gt;XML&lt;/span&gt; document to be considered&amp;nbsp;malformed!&lt;/p&gt;
&lt;p&gt;And so, after some dabbling, &lt;span class="caps"&gt;XHTML&lt;/span&gt; was largely forgotten.  Its legacy lives on in two&amp;nbsp;ways:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;It got us all to stop using uppercase tag names!  So long &lt;code&gt;&amp;lt;BODY&amp;gt;&lt;/code&gt;, hello &lt;code&gt;&amp;lt;body&amp;gt;&lt;/code&gt;.  &lt;span class="caps"&gt;XML&lt;/span&gt; is case-sensitive, you see, and all the &lt;span class="caps"&gt;XHTML&lt;/span&gt; tags were defined in lowercase, so uppercase tags simply would not work.  (Fun fact: to this day, JavaScript APIs report &lt;span class="caps"&gt;HTML&lt;/span&gt; tag names in uppercase.)  The increased popularity of syntax highlighting probably also had something to do with this; we weren&amp;#8217;t all still using Notepad as we had been in&amp;nbsp;1997.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;A bunch of folks &lt;em&gt;still&lt;/em&gt; think self-closing tags are necessary.  You see, &lt;span class="caps"&gt;HTML&lt;/span&gt; has two kinds of tags: containers like &lt;code&gt;&amp;lt;p&amp;gt;...&amp;lt;/p&amp;gt;&lt;/code&gt; and markers like &lt;code&gt;&amp;lt;br&amp;gt;&lt;/code&gt;.  Since a &lt;code&gt;&amp;lt;br&amp;gt;&lt;/code&gt; can&amp;#8217;t possibly contain anything, there&amp;#8217;s no such thing as &lt;code&gt;&amp;lt;/br&amp;gt;&lt;/code&gt;.  &lt;span class="caps"&gt;XML&lt;/span&gt;, as a generic grammar, doesn&amp;#8217;t have this distinction; every tag &lt;em&gt;must&lt;/em&gt; be closed, but as a shortcut, you can write &lt;code&gt;&amp;lt;br/&amp;gt;&lt;/code&gt; to mean &lt;code&gt;&amp;lt;br&amp;gt;&amp;lt;/br&amp;gt;&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;&lt;span class="caps"&gt;XHTML&lt;/span&gt; has been dead for years, but for some reason, I still see folks write &lt;code&gt;&amp;lt;br/&amp;gt;&lt;/code&gt; in regular &lt;span class="caps"&gt;HTML&lt;/span&gt; documents.  Outside of &lt;span class="caps"&gt;XML&lt;/span&gt;, that slash doesn&amp;#8217;t do anything; &lt;span class="caps"&gt;HTML5&lt;/span&gt; has defined it for compatibility reasons, but it&amp;#8217;s silently ignored.  It&amp;#8217;s even actively harmful, since it might lead you to believe that &lt;code&gt;&amp;lt;script/&amp;gt;&lt;/code&gt; is an empty &lt;code&gt;&amp;lt;script&amp;gt;&lt;/code&gt; tag — but in &lt;span class="caps"&gt;HTML&lt;/span&gt;, it definitely is&amp;nbsp;not!&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;I do miss one thing about &lt;span class="caps"&gt;XHTML&lt;/span&gt;.  You could combine it with &lt;span class="caps"&gt;XSLT&lt;/span&gt;, the &lt;span class="caps"&gt;XML&lt;/span&gt; templating meta-language, to do in-browser templating (i.e., slot page-specific contents into your overall site layout) with no scripting required.  It&amp;#8217;s the &lt;em&gt;only&lt;/em&gt; way that&amp;#8217;s ever been possible, and it was cool as all hell when it worked, but the drawbacks were too severe when it didn&amp;#8217;t.  Also, &lt;span class="caps"&gt;XSLT&lt;/span&gt; is totally fucking&amp;nbsp;incomprehensible.&lt;/p&gt;
&lt;h3 id="the-beginning-of-css-layout"&gt;&lt;a class="toclink" href="#the-beginning-of-css-layout"&gt;The beginning of CSS&amp;nbsp;layout&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Back to &lt;span class="caps"&gt;CSS&lt;/span&gt;!&lt;/p&gt;
&lt;p&gt;You&amp;#8217;re an aspiring web designer.  For whatever reason, you want to try using this &lt;span class="caps"&gt;CSS&lt;/span&gt; thing to lay out your whole page, even though it was &lt;em&gt;clearly&lt;/em&gt; intended just for colors and stuff.  What do you&amp;nbsp;do?&lt;/p&gt;
&lt;p&gt;As I mentioned before, your core problem is &lt;em&gt;putting things next to each other&lt;/em&gt;.  Putting things on &lt;em&gt;top&lt;/em&gt; of each other is a non-problem — that&amp;#8217;s the normal behavior of &lt;span class="caps"&gt;HTML&lt;/span&gt;.  The whole reason everyone uses tables is that you can slop stuff into table cells and have it laid out side-by-side, in&amp;nbsp;columns.&lt;/p&gt;
&lt;p&gt;Well, tables seem to be out.  &lt;span class="caps"&gt;CSS&lt;/span&gt; 2 had added some element display modes that corresponded to the parts of a table, but to use them, you&amp;#8217;d have to have the same three levels of nesting as real tables: the table itself, then a row, then a cell.  That doesn&amp;#8217;t seem like a huge step up, and anyway, &lt;span class="caps"&gt;IE&lt;/span&gt; won&amp;#8217;t support them until the distant&amp;nbsp;future.&lt;/p&gt;
&lt;p&gt;There&amp;#8217;s that &lt;code&gt;position&lt;/code&gt; thing, but it seems to make things &lt;em&gt;overlap&lt;/em&gt; more often than not.&amp;nbsp;Hmm.&lt;/p&gt;
&lt;p&gt;What does that&amp;nbsp;leave?&lt;/p&gt;
&lt;p&gt;Only one tool, really: &lt;code&gt;float&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;I said that &lt;code&gt;float&lt;/code&gt; was intended for magazine-style &amp;#8220;pull&amp;#8221; images, which is true, but &lt;span class="caps"&gt;CSS&lt;/span&gt; had defined it fairly generically.  In &lt;em&gt;principle&lt;/em&gt;, it could be applied to any element.  If you wanted a sidebar, you could tell it to float to the left and be 20% the width of the page, and you&amp;#8217;d get something like&amp;nbsp;this:&lt;/p&gt;
&lt;table class="highlighttable"&gt;&lt;tr&gt;&lt;td class="linenos"&gt;&lt;div class="linenodiv"&gt;&lt;pre&gt;&lt;span class="normal"&gt;1&lt;/span&gt;
&lt;span class="normal"&gt;2&lt;/span&gt;
&lt;span class="normal"&gt;3&lt;/span&gt;
&lt;span class="normal"&gt;4&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/td&gt;&lt;td class="code"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;+---------+
| sidebar | Hello, and welcome to my website!
|         |
+---------+
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;p&gt;Alas!  Floating has the secondary behavior that text wraps around it.  If your page text was ever longer than your sidebar, it would wrap around &lt;em&gt;underneath&lt;/em&gt; the sidebar, and the illusion would shatter.  But hey, no problem.  &lt;span class="caps"&gt;CSS&lt;/span&gt; specified that floats don&amp;#8217;t wrap around each other, so all you needed to do was float the body as&amp;nbsp;well!&lt;/p&gt;
&lt;table class="highlighttable"&gt;&lt;tr&gt;&lt;td class="linenos"&gt;&lt;div class="linenodiv"&gt;&lt;pre&gt;&lt;span class="normal"&gt;1&lt;/span&gt;
&lt;span class="normal"&gt;2&lt;/span&gt;
&lt;span class="normal"&gt;3&lt;/span&gt;
&lt;span class="normal"&gt;4&lt;/span&gt;
&lt;span class="normal"&gt;5&lt;/span&gt;
&lt;span class="normal"&gt;6&lt;/span&gt;
&lt;span class="normal"&gt;7&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/td&gt;&lt;td class="code"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="nb"&gt;+---------+&lt;/span&gt;&lt;span class="c"&gt; &lt;/span&gt;&lt;span class="nb"&gt;+-----------------------------------+&lt;/span&gt;&lt;span class="c"&gt;&lt;/span&gt;
&lt;span class="c"&gt;| sidebar | | Hello&lt;/span&gt;&lt;span class="nt"&gt;,&lt;/span&gt;&lt;span class="c"&gt; and welcome to my website! |&lt;/span&gt;
&lt;span class="c"&gt;|         | |                                   |&lt;/span&gt;
&lt;span class="nb"&gt;+---------+&lt;/span&gt;&lt;span class="c"&gt; | Here&amp;#39;s a longer paragraph to show |&lt;/span&gt;
&lt;span class="c"&gt;            | that my galaxy brain CSS float    |&lt;/span&gt;
&lt;span class="c"&gt;            | nonsense prevents text wrap&lt;/span&gt;&lt;span class="nt"&gt;.&lt;/span&gt;&lt;span class="c"&gt;      |&lt;/span&gt;
&lt;span class="c"&gt;            &lt;/span&gt;&lt;span class="nb"&gt;+-----------------------------------+&lt;/span&gt;&lt;span class="c"&gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;p&gt;This approach worked, but its limitations were much more obvious than those of tables.  If you added a footer, for example, then it would try to fit to the &lt;em&gt;right&lt;/em&gt; of the body text — remember, all of that is &amp;#8220;pull&amp;#8221; floats, so as far as the browser is concerned, the &amp;#8220;cursor&amp;#8221; is still at the top.  So now you need to use &lt;code&gt;clear&lt;/code&gt;, which bumps an element down below all floats, to fix that.  And if you made the sidebar 20% wide and the body 80% wide, then any margin between them would add to that 100%, making the page wider than the viewport, so now you have an ugly horizontal scrollbar, so you have to do some goofy math to fix that as well.  If you have borders or backgrounds on either part, then it was a little conspicuous that they were different heights, so now you have to do some &lt;em&gt;truly&lt;/em&gt; grotesque stuff to fix &lt;em&gt;that&lt;/em&gt;.  And the more conscientious authors noticed that screenreaders would read the entire sidebar before getting to the body text, which is a pretty rude thing to subject blind visitors to, so they came up with yet &lt;em&gt;more&lt;/em&gt; elaborate setups to have a three-column layout with the middle column appearing first in the &lt;span class="caps"&gt;HTML&lt;/span&gt;.&lt;/p&gt;
&lt;p&gt;The result was a design that looked nice and worked well and scaled correctly, but backed by a weird mess of &lt;span class="caps"&gt;CSS&lt;/span&gt;.  None of what you were &lt;em&gt;writing&lt;/em&gt; actually corresponded to what you &lt;em&gt;wanted&lt;/em&gt; — these are major parts of your design, not one-off pull quotes!  It was difficult to understand the relationship between the layout-related &lt;span class="caps"&gt;CSS&lt;/span&gt; and what appeared on the screen, and that would get much worse before it got&amp;nbsp;better.&lt;/p&gt;
&lt;h3 id="thumbnail-grid-2"&gt;&lt;a class="toclink" href="#thumbnail-grid-2"&gt;Thumbnail grid&amp;nbsp;2&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Armed with a new toy, we can improve that thumbnail grid.  The original table-based layout was, even if you don&amp;#8217;t care about tag semantics, incredibly tedious.  Now we can do&amp;nbsp;better!&lt;/p&gt;
&lt;table class="highlighttable"&gt;&lt;tr&gt;&lt;td class="linenos"&gt;&lt;div class="linenodiv"&gt;&lt;pre&gt;&lt;span class="normal"&gt;1&lt;/span&gt;
&lt;span class="normal"&gt;2&lt;/span&gt;
&lt;span class="normal"&gt;3&lt;/span&gt;
&lt;span class="normal"&gt;4&lt;/span&gt;
&lt;span class="normal"&gt;5&lt;/span&gt;
&lt;span class="normal"&gt;6&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/td&gt;&lt;td class="code"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;ul&lt;/span&gt; &lt;span class="na"&gt;class&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;thumbnail-grid&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;li&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;img&lt;/span&gt; &lt;span class="na"&gt;src&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;...&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;br&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;caption&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;li&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;li&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;img&lt;/span&gt; &lt;span class="na"&gt;src&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;...&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;br&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;caption&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;li&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;li&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;img&lt;/span&gt; &lt;span class="na"&gt;src&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;...&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;br&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;caption&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;li&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
    ...
&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;ul&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;p&gt;This is the dream of &lt;span class="caps"&gt;CSS&lt;/span&gt;: your &lt;span class="caps"&gt;HTML&lt;/span&gt; contains the page data in some sensible form, and then &lt;span class="caps"&gt;CSS&lt;/span&gt; describes how it actually&amp;nbsp;looks.&lt;/p&gt;
&lt;p&gt;Unfortunately, with &lt;code&gt;float&lt;/code&gt; as the only tool available to us, the results are a bit rough.  This &lt;a href="https://eev.ee/media/2020-02-css/thumbnail-grids.html#floats"&gt;new version&lt;/a&gt; does adapt better to various screen sizes, but it requires some hacks: the cells have to be a fixed height, centering the whole grid is fairly complicated, and the grid effect falls apart entirely with wider elements.  It&amp;#8217;s becoming clear that what we wanted is something more like a table, but with a flexible number of columns.  This is just faking&amp;nbsp;it.&lt;/p&gt;
&lt;p&gt;You also need this weird &amp;#8220;clearfix&amp;#8221; thing, an incantation that would become infamous during this era.  Remember that a float doesn&amp;#8217;t move the &amp;#8220;cursor&amp;#8221; — a fake idea I&amp;#8217;m using, but close enough.  That means that this &lt;code&gt;&amp;lt;ul&amp;gt;&lt;/code&gt;, which is full &lt;em&gt;only&lt;/em&gt; of floated elements, has no height at all.  It ends exactly where it begins, with all the floated thumbnails spilling out below it.  Worse, because any subsequent elements don&amp;#8217;t have any floated &lt;em&gt;siblings&lt;/em&gt;, they&amp;#8217;ll ignore the thumbnails entirely and render normally from just below the empty &amp;#8220;grid&amp;#8221; — producing an overlapping&amp;nbsp;mess!&lt;/p&gt;
&lt;p&gt;The solution is to add a dummy element at the &lt;em&gt;end&lt;/em&gt; of the list which takes up no space, but has the &lt;span class="caps"&gt;CSS&lt;/span&gt; &lt;code&gt;clear: both&lt;/code&gt; — bumping it down below all floats.  That effectively pushes the bottom of the &lt;code&gt;&amp;lt;ul&amp;gt;&lt;/code&gt; under all the individual thumbnails, so it fits snugly around&amp;nbsp;them.&lt;/p&gt;
&lt;p&gt;Browsers would later support the &lt;code&gt;::before&lt;/code&gt; and &lt;code&gt;::after&lt;/code&gt; &lt;span class="dquo"&gt;&amp;#8220;&lt;/span&gt;generated content&amp;#8221; pseudo-elements, which let us avoid the dummy element entirely.  Stylesheets from the mid-00s were often littered with stuff like&amp;nbsp;this:&lt;/p&gt;
&lt;table class="highlighttable"&gt;&lt;tr&gt;&lt;td class="linenos"&gt;&lt;div class="linenodiv"&gt;&lt;pre&gt;&lt;span class="normal"&gt;1&lt;/span&gt;
&lt;span class="normal"&gt;2&lt;/span&gt;
&lt;span class="normal"&gt;3&lt;/span&gt;
&lt;span class="normal"&gt;4&lt;/span&gt;
&lt;span class="normal"&gt;5&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/td&gt;&lt;td class="code"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;thumbnail-grid&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nd"&gt;after&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;content&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;display&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;block&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;clear&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;both&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;p&gt;Still, it was better than&amp;nbsp;tables.&lt;/p&gt;
&lt;h3 id="dhtml"&gt;&lt;a class="toclink" href="#dhtml"&gt;DHTML&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;As a quick aside into the world of JavaScript, the newfangled &lt;code&gt;position&lt;/code&gt; property &lt;em&gt;did&lt;/em&gt; give us the ability to do some layout things dynamically.  I heartily oppose such heresy, not least because no one has ever actually done it right, but it was nice for some&amp;nbsp;toys.&lt;/p&gt;
&lt;p&gt;Thus began the era of &amp;#8220;dynamic &lt;span class="caps"&gt;HTML&lt;/span&gt;&amp;#8221; — i.e., &lt;span class="caps"&gt;HTML&lt;/span&gt; affected by JavaScript, a term that has fallen entirely out of favor because we can&amp;#8217;t even make a fucking static blog without JavaScript any more.  In the early days it was much more innocuous, with teenagers putting sparkles that trailed behind your mouse cursor or little analog clocks that ticked by in real&amp;nbsp;time.&lt;/p&gt;
&lt;p&gt;The most popular source of these things was &lt;a href="http://www.dynamicdrive.com/"&gt;Dynamic Drive&lt;/a&gt;, a site that miraculously still exists and probably has a bunch of toys not updated since the early&amp;nbsp;00s.&lt;/p&gt;
&lt;p&gt;But if you don&amp;#8217;t like digging, here&amp;#8217;s an example: every year (except this year when I forgot oops), I like to add confetti and other nonsense to my blog on my birthday.  I&amp;#8217;m very lazy so I started this tradition by using &lt;a href="http://www.schillmania.com/projects/snowstormv12_20041121a/script/snowstorm.js"&gt;this script I found somewhere&lt;/a&gt;, originally intended for snowflakes.  It works by placing a bunch of images on the page, giving them &lt;code&gt;position: absolute&lt;/code&gt;, and meticulously altering their coordinates over and&amp;nbsp;over.&lt;/p&gt;
&lt;p&gt;Contrast this with &lt;a href="https://c.eev.ee/PARTYMODE/"&gt;the version I wrote from scratch a couple years ago&lt;/a&gt;, which has only a &lt;a href="https://c.eev.ee/PARTYMODE/partymode.js"&gt;tiny bit of &lt;span class="caps"&gt;JS&lt;/span&gt;&lt;/a&gt; to set up the images, then lets the browser animate them with &lt;span class="caps"&gt;CSS&lt;/span&gt;.  It&amp;#8217;s slightly less featureful, but lets the browser do all the work, possibly even with hardware acceleration.  How far we&amp;#8217;ve&amp;nbsp;come.&lt;/p&gt;
&lt;h2 id="web-20"&gt;&lt;a class="toclink" href="#web-20"&gt;Web&amp;nbsp;2.0&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Dark times can&amp;#8217;t last forever.  A combination of factors dragged us towards the&amp;nbsp;light.&lt;/p&gt;
&lt;p&gt;One of the biggest was &lt;a href="https://www.mozilla.org/en-US/firefox/"&gt;Firefox&lt;/a&gt; — or, if you were cool, originally Phoenix and then Firebird — which hit 1.0 in Nov &amp;#8216;04 and went on to take a serious bite out of &lt;span class="caps"&gt;IE&lt;/span&gt;.  That rewritten Netscape 6 browser core, the heart of the Mozilla Suite, had been extracted into a standalone browser.  It was quick, it was simple, it was much more standard-compliant, and absolutely none of that&amp;nbsp;mattered.&lt;/p&gt;
&lt;p&gt;No, Firefox really got a foothold because it had &lt;em&gt;tabs&lt;/em&gt;.  &lt;span class="caps"&gt;IE&lt;/span&gt; 6 did not have tabs; if you wanted to open a second webpage, you opened another window.  It fucking sucked, man.  Firefox was a&amp;nbsp;miracle.&lt;/p&gt;
&lt;p&gt;Firefox wasn&amp;#8217;t the first tabbed browser, of course; the full Mozilla Suite&amp;#8217;s browser had them, and the obscure (but scrappy!) Opera had had them for ages.  But it was Firefox that took off, for various reasons, not least of which was that it didn&amp;#8217;t have a giant fucking ad bar at the top like Opera&amp;nbsp;did.&lt;/p&gt;
&lt;p&gt;Designers did push for Firefox on standards grounds, of course; it&amp;#8217;s just that that angle primarily appealed to other designers, not so much to their parents.  One of the most popular and spectacular demonstrations was the &lt;a href="https://en.wikipedia.org/wiki/Acid2"&gt;Acid2 test&lt;/a&gt;, intended to test a variety of features of then-modern Web standards.  It had the advantage of producing a cute smiley face when rendered correctly, and a &lt;a href="https://en.wikipedia.org/wiki/File:Ieacid2.png"&gt;fucking nightmare hellscape&lt;/a&gt; in &lt;span class="caps"&gt;IE&lt;/span&gt; 6.  Early Firefox wasn&amp;#8217;t perfect, but it was certainly much closer, and you could &lt;em&gt;see&lt;/em&gt; it make progress until it fully passed with the release of Firefox&amp;nbsp;3.&lt;/p&gt;
&lt;p&gt;It also helped that Firefox had a faster JavaScript engine, even before &lt;span class="caps"&gt;JIT&lt;/span&gt; caught on.  Much, much faster.  Like, as I recall, &lt;span class="caps"&gt;IE&lt;/span&gt; 6 implemented &lt;code&gt;getElementById&lt;/code&gt; by iterating over the entire document, even though IDs are unique.  Glance at some &lt;a href="https://blog.jquery.com/2011/01/31/jquery-15-released/"&gt;old jQuery release announcements&lt;/a&gt;; they usually have some performance charts, and everything else absolutely &lt;em&gt;dwarfs&lt;/em&gt; &lt;span class="caps"&gt;IE&lt;/span&gt; 6 through&amp;nbsp;8.&lt;/p&gt;
&lt;p&gt;Oh, and there was that whole thing where &lt;span class="caps"&gt;IE&lt;/span&gt; 6 was a giant walking security hole, especially with its native support for arbitrary binary components that only needed a &amp;#8220;yes&amp;#8221; click on an arcane dialog to get full and unrestricted access to your system.  Probably didn&amp;#8217;t help its&amp;nbsp;reputation.&lt;/p&gt;
&lt;p&gt;Anyway, with something other than &lt;span class="caps"&gt;IE&lt;/span&gt; taking over serious market share, even the most ornery designers couldn&amp;#8217;t just target &lt;span class="caps"&gt;IE&lt;/span&gt; 6 and call it a day any more.  Now there was a &lt;em&gt;reason&lt;/em&gt; to use strict mode, a reason to care about compatibility and standards — which Firefox was making a constant effort to follow better, while &lt;span class="caps"&gt;IE&lt;/span&gt; 6 remained&amp;nbsp;stagnant.&lt;/p&gt;
&lt;p&gt;(I&amp;#8217;d argue that this effect opened the door for &lt;span class="caps"&gt;OS&lt;/span&gt; X to make some inroads, and also for the iPhone to exist at all.  I&amp;#8217;m not kidding!  Think about it; if the iPhone browser hadn&amp;#8217;t actually worked with anything because everyone was still targeting &lt;span class="caps"&gt;IE&lt;/span&gt; 6, it&amp;#8217;d basically have been a more expensive Palm.  Remember, at first Apple didn&amp;#8217;t even want native apps; it bet on the&amp;nbsp;Web.)&lt;/p&gt;
&lt;p&gt;(Speaking of which, Safari was released in Jan &amp;#8216;03, based on a fork of the &lt;span class="caps"&gt;KHTML&lt;/span&gt; engine used in &lt;span class="caps"&gt;KDE&lt;/span&gt;&amp;#8217;s Konqueror browser.  I think I was using &lt;span class="caps"&gt;KDE&lt;/span&gt; at the time, so this was very exciting, but no one else really cared about &lt;span class="caps"&gt;OS&lt;/span&gt; X and its 2% market&amp;nbsp;share.)&lt;/p&gt;
&lt;p&gt;Another major factor appeared on April Fools&amp;#8217; Day, 2004, when Google announced Gmail.  Ha, ha!  A funny joke.  Webmail that isn&amp;#8217;t terrible?  That&amp;#8217;s a good one,&amp;nbsp;Google.&lt;/p&gt;
&lt;p&gt;Oh.  Oh, fuck.  Oh they&amp;#8217;re not kidding.  &lt;em&gt;How the fuck does this even&amp;nbsp;work&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;The answer, as every web dev now knows, is XMLHttpRequest — named for the fact that nobody has ever once used it to request &lt;span class="caps"&gt;XML&lt;/span&gt;.  Apparently it was invented by Microsoft for use with Exchange, then cloned early on by Mozilla, but I&amp;#8217;m just reading this from &lt;a href="https://en.wikipedia.org/wiki/XMLHttpRequest"&gt;Wikipedia&lt;/a&gt; and you can do that&amp;nbsp;yourself.&lt;/p&gt;
&lt;p&gt;The important thing is, it lets you make an &lt;span class="caps"&gt;HTTP&lt;/span&gt; request from JavaScript.  You could now update only &lt;em&gt;part&lt;/em&gt; a page with new data, completely in the background, without reloading.  &lt;em&gt;Nobody&lt;/em&gt; had heard of this thing before, so when Google dropped an entire email client based on it, it was like fucking&amp;nbsp;magic.&lt;/p&gt;
&lt;p&gt;Arguably the whole thing was a mistake and has led to a hell future where static pages load three paragraphs of text in the background using &lt;span class="caps"&gt;XHR&lt;/span&gt; for no goddamn reason, but that&amp;#8217;s a &lt;a href="https://eev.ee/blog/2016/03/06/maybe-we-could-tone-down-the-javascript/"&gt;different post&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Along similar lines, August 2006 saw the release of &lt;a href="https://jquery.com/"&gt;jQuery&lt;/a&gt;, a similar miracle.  Not only did it paper over the differences between &lt;span class="caps"&gt;IE&lt;/span&gt;&amp;#8217;s &amp;#8220;JScript&amp;#8221; APIs and the standard approaches taken by everyone else (which had been done before by other libraries), but it made it very easy to work with whole &lt;em&gt;groups&lt;/em&gt; of elements at a time, something that had historically been a huge pain in the ass.  Now you could fairly easily apply &lt;span class="caps"&gt;CSS&lt;/span&gt; all over the place from JavaScript!  Which is a bad idea!  But everything was so bad that we did it&amp;nbsp;anyway!&lt;/p&gt;
&lt;p&gt;Hold on, I hear you cry.  These things are about JavaScript!  Isn&amp;#8217;t this a post about &lt;span class="caps"&gt;CSS&lt;/span&gt;?&lt;/p&gt;
&lt;p&gt;You&amp;#8217;re absolutely right!  I mention the rise of JavaScript because I think it led directly to the modern state of &lt;span class="caps"&gt;CSS&lt;/span&gt;, thanks to an increase in one big&amp;nbsp;factor:&lt;/p&gt;
&lt;h3 id="ambition"&gt;&lt;a class="toclink" href="#ambition"&gt;Ambition&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Firefox showed us that we could have browsers that actually, like, &lt;em&gt;improve&lt;/em&gt; — every new improvement on Acid2 was exciting.  Gmail showed us that the Web could do more than show plain text with snowflakes in&amp;nbsp;front.&lt;/p&gt;
&lt;p&gt;And folks started itching to get &lt;em&gt;fancy&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;The problem was, browsers hadn&amp;#8217;t really gotten any better yet.  Firefox was faster in some respects, and it adhered more closely to the &lt;span class="caps"&gt;CSS&lt;/span&gt; spec, but it didn&amp;#8217;t fundamentally do anything that browsers weren&amp;#8217;t supposed to be able to do already.  Only the &lt;em&gt;tooling&lt;/em&gt; had improved, and that mostly affected JavaScript.  &lt;span class="caps"&gt;CSS&lt;/span&gt; was a static language, so you couldn&amp;#8217;t write a library to make it better.  Generating &lt;span class="caps"&gt;CSS&lt;/span&gt; with JavaScript was a possibility, but boy oh boy is that ever a bad&amp;nbsp;idea.&lt;/p&gt;
&lt;p&gt;Another problem was that &lt;span class="caps"&gt;CSS&lt;/span&gt; 2 was only really good at styling rectangles.  That was fine in the 90s, when every &lt;span class="caps"&gt;OS&lt;/span&gt; had the aesthetic of rectangles containing more rectangles.  But now we were in the days of Windows &lt;span class="caps"&gt;XP&lt;/span&gt; and &lt;span class="caps"&gt;OS&lt;/span&gt; X, where everything was shiny and glossy and made of curvy plastic.  It was a little embarrassing to have rounded corners and neatly shaded swooshes in your &lt;em&gt;file browser&lt;/em&gt; and nowhere on the&amp;nbsp;Web.&lt;/p&gt;
&lt;p&gt;Thus began a new reign of&amp;nbsp;darkness.&lt;/p&gt;
&lt;h3 id="the-era-of-css-hacks"&gt;&lt;a class="toclink" href="#the-era-of-css-hacks"&gt;The era of CSS&amp;nbsp;hacks&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Designers wanted a lot of things that &lt;span class="caps"&gt;CSS&lt;/span&gt; just could not&amp;nbsp;offer.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Round corners were a big one.  Square corners had fallen out of vogue, and now everyone wanted buttons with round corners, since they were The Future.  (Native buttons also went out of vogue, for some reason.)  Alas, &lt;span class="caps"&gt;CSS&lt;/span&gt; had no way to do this.  Your options&amp;nbsp;were:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;Make a fixed-size background image of a rounded rectangle and put it on a fixed-size button.  Maybe drop the text altogether  and just make the whole thing an image.&amp;nbsp;Eugh.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Make a &lt;em&gt;generic&lt;/em&gt; background image and scale it to fit.  More clever, but the corners might end up not&amp;nbsp;round.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Make the rounded rectangle, cut out the corner and edges, and put them in a 3×3 table with the button label in the middle.  Even better, use JavaScript to do this on the&amp;nbsp;fly.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Fuck it, make your entire website one big Flash app&amp;nbsp;lol&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Another problem was that &lt;span class="caps"&gt;IE&lt;/span&gt; 6 didn&amp;#8217;t understand PNGs with 8-bit alpha; it could only correctly display PNGs with 1-bit alpha, i.e. every pixel is either fully opaque or fully transparent, like GIFs.  You had to settle for jagged edges, bake a solid background color into the image, or apply various fixes that centered around this fucking garbage&amp;nbsp;nonsense:&lt;/p&gt;
&lt;table class="highlighttable"&gt;&lt;tr&gt;&lt;td class="linenos"&gt;&lt;div class="linenodiv"&gt;&lt;pre&gt;&lt;span class="normal"&gt;1&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/td&gt;&lt;td class="code"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="nt"&gt;filter&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;progid&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="nd"&gt;DXImageTransform&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Microsoft&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;AlphaImageLoader&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nt"&gt;src&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;bite-my-ass.png&amp;#39;&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Along similar lines: gradients and drop shadows!  You can&amp;#8217;t have fancy plastic buttons without those.  But here you were basically stuck with making images&amp;nbsp;again.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Translucency was a bit of a mess.  Most browsers supported the &lt;span class="caps"&gt;CSS&lt;/span&gt; 3 &lt;code&gt;opacity&lt;/code&gt; property since very early on&amp;#8230;  except &lt;span class="caps"&gt;IE&lt;/span&gt;, which needed another wacky Microsoft-specific &lt;code&gt;filter&lt;/code&gt; thing.  And if you wanted &lt;em&gt;only&lt;/em&gt; the background translucent, you&amp;#8217;d need a translucent &lt;span class="caps"&gt;PNG&lt;/span&gt;, which&amp;#8230;  well, you&amp;nbsp;know.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Since the beginning, jQuery shipped with built-in animated effects like &lt;code&gt;fadeIn&lt;/code&gt;, and they started popping up all over the place.  It was kind of like the Web equivalent of how every Linux user in the mid-00s (and I include myself in this) used that fucking &lt;a href="https://youtu.be/4QokOwvPxrE?t=118"&gt;Compiz cube effect&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Obviously you need JavaScript to trigger an element&amp;#8217;s disappearance in most interesting cases, but using it to control the actual animation was a bit heavy-handed and put a strain on browsers.  Tabbed browsing compounded this, since browsers were largely single-threaded, and for various reasons, every open page ran in the same&amp;nbsp;thread.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Oh!  Alternating background colors on table rows.  This has since gone out of style, but I think that&amp;#8217;s a shame, because &lt;em&gt;man&lt;/em&gt; did it make tables easier to read.  But &lt;span class="caps"&gt;CSS&lt;/span&gt; had no answer for this, so you had to either give every other row a class like &lt;code&gt;&amp;lt;tr class="odd"&amp;gt;&lt;/code&gt; (hope the table&amp;#8217;s generated with code!) or do some jQuery&amp;nbsp;nonsense.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;span class="caps"&gt;CSS&lt;/span&gt; 2 introduced the &lt;code&gt;&amp;gt;&lt;/code&gt; child selector, so you could write stuff like &lt;code&gt;ul.foo &amp;gt; li&lt;/code&gt; to style special lists without messing up nested lists, and &lt;span class="caps"&gt;IE&lt;/span&gt; 6!  Didn&amp;#8217;t!  Fucking!  Support!&amp;nbsp;It!&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;All those are merely aesthetic concerns, though.  If you were interested in layout, well, the rise of Firefox had made your life at once much easier and much&amp;nbsp;harder.&lt;/p&gt;
&lt;p&gt;Remember &lt;code&gt;inline-block&lt;/code&gt;?  Firefox 2 actually supported it!  It was buggy and hidden behind a vendor prefix, but it more or less worked, which let designers start playing with it.  And then Firefox 3 supported it more or less fully, which felt miraculous.  Version 3 of our &lt;a href="https://eev.ee/media/2020-02-css/thumbnail-grids.html#inline-block"&gt;thumbnail grid&lt;/a&gt; is as simple as a width and &lt;code&gt;inline-block&lt;/code&gt;:&lt;/p&gt;
&lt;table class="highlighttable"&gt;&lt;tr&gt;&lt;td class="linenos"&gt;&lt;div class="linenodiv"&gt;&lt;pre&gt;&lt;span class="normal"&gt;1&lt;/span&gt;
&lt;span class="normal"&gt;2&lt;/span&gt;
&lt;span class="normal"&gt;3&lt;/span&gt;
&lt;span class="normal"&gt;4&lt;/span&gt;
&lt;span class="normal"&gt;5&lt;/span&gt;
&lt;span class="normal"&gt;6&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/td&gt;&lt;td class="code"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;thumbnails&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;li&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;display&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;inline-block&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;width&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;250&lt;/span&gt;&lt;span class="kt"&gt;px&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;margin&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;0.5&lt;/span&gt;&lt;span class="kt"&gt;em&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;vertical-align&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;top&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;p&gt;The general idea of &lt;code&gt;inline-block&lt;/code&gt; is that the &lt;em&gt;inside&lt;/em&gt; acts like a block, but the block itself is placed in regular flowing text, like an image.  Each thumbnail is thus contained in a box, but the boxes all lie next to each other, and because of their equal widths, they flow into a grid.  And since it&amp;#8217;s functionally a line of text, you don&amp;#8217;t have to work around any weird impact on the rest of the page like you had to do with&amp;nbsp;floats.&lt;/p&gt;
&lt;p&gt;Sure, this had some drawbacks.  You couldn&amp;#8217;t do anything with the leftover space, for example, so there was a risk of a big empty void on the right with pathological screen sizes.  You still had the problem of breaking the grid with a wide cell.  But at least it&amp;#8217;s not&amp;nbsp;floats.&lt;/p&gt;
&lt;p&gt;One teeny problem: &lt;span class="caps"&gt;IE&lt;/span&gt; 6.  It did &lt;em&gt;technically&lt;/em&gt; support &lt;code&gt;inline-block&lt;/code&gt;, but only on elements that were naturally &lt;code&gt;inline&lt;/code&gt; — ones like &lt;code&gt;&amp;lt;b&amp;gt;&lt;/code&gt; and &lt;code&gt;&amp;lt;i&amp;gt;&lt;/code&gt;, not &lt;code&gt;&amp;lt;li&amp;gt;&lt;/code&gt;.  So, not ones you&amp;#8217;d actually want (or think) to use &lt;code&gt;inline-block&lt;/code&gt; on.&amp;nbsp;Sigh.&lt;/p&gt;
&lt;p&gt;Lucky for us, at some point an absolute genius discovered &lt;code&gt;hasLayout&lt;/code&gt;, an internal optimization in &lt;span class="caps"&gt;IE&lt;/span&gt; that marks whether an element&amp;#8230;  uh&amp;#8230;  has&amp;#8230;  layout.  Look, I don&amp;#8217;t know.  Basically it changes the rendering path for an element — making it &lt;em&gt;differently&lt;/em&gt; buggy, like quirks mode on a per-element basis!  The upshot is that the above works in &lt;span class="caps"&gt;IE&lt;/span&gt; 6 if you add a couple&amp;nbsp;lines:&lt;/p&gt;
&lt;table class="highlighttable"&gt;&lt;tr&gt;&lt;td class="linenos"&gt;&lt;div class="linenodiv"&gt;&lt;pre&gt;&lt;span class="normal"&gt;1&lt;/span&gt;
&lt;span class="normal"&gt;2&lt;/span&gt;
&lt;span class="normal"&gt;3&lt;/span&gt;
&lt;span class="normal"&gt;4&lt;/span&gt;
&lt;span class="normal"&gt;5&lt;/span&gt;
&lt;span class="normal"&gt;6&lt;/span&gt;
&lt;span class="normal"&gt;7&lt;/span&gt;
&lt;span class="normal"&gt;8&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/td&gt;&lt;td class="code"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;thumbnails&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;li&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;display&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;inline-block&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;width&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;250&lt;/span&gt;&lt;span class="kt"&gt;px&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;margin&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;0.5&lt;/span&gt;&lt;span class="kt"&gt;em&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;vertical-align&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;top&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="err"&gt;*&lt;/span&gt;&lt;span class="n"&gt;zoom&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="err"&gt;*&lt;/span&gt;&lt;span class="k"&gt;display&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;inline&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;p&gt;The leading asterisks make the property invalid, so browsers should ignore the whole line&amp;#8230;  but for some reason I cannot begin to fathom, &lt;span class="caps"&gt;IE&lt;/span&gt; 6 ignores the asterisks and accepts the rest of the rule.  (Almost any punctuation worked, including a hyphen or — my personal favorite — an underscore.)  The &lt;code&gt;zoom&lt;/code&gt; property is a Microsoft extension that scales stuff, with the side effect that it grants the mystical property of &amp;#8220;layout&amp;#8221; to the element as well.  And &lt;code&gt;display: inline&lt;/code&gt; &lt;em&gt;should&lt;/em&gt; make each element spill its contents into one big line of text, but &lt;span class="caps"&gt;IE&lt;/span&gt; treats an &lt;code&gt;inline&lt;/code&gt; element that has &amp;#8220;layout&amp;#8221; roughly like an &lt;code&gt;inline-block&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;And here we saw the true potential of &lt;span class="caps"&gt;CSS&lt;/span&gt; messes.  Browser-specific rules, with deliberate bad syntax that one browser would ignore, to replicate an effect that &lt;em&gt;still&lt;/em&gt; isn&amp;#8217;t clearly described by what you&amp;#8217;re writing.  &lt;a href="https://blog.mozilla.org/webdev/2009/02/20/cross-browser-inline-block/"&gt;Entire tutorials&lt;/a&gt; written to explain how to accomplish something simple, like a &lt;em&gt;grid&lt;/em&gt;, but have it actually work on most people&amp;#8217;s browsers.  You&amp;#8217;d also see &lt;code&gt;* html&lt;/code&gt;, &lt;code&gt;html &amp;gt; /**/ body&lt;/code&gt;, and all kinds of other nonsense.  &lt;a href="http://browserhacks.com/"&gt;Here&amp;#8217;s a full list!&lt;/a&gt;  And remember that &amp;#8220;clearfix&amp;#8221; hack from before?  The &lt;a href="https://css-tricks.com/snippets/css/clear-fix/"&gt;full version&lt;/a&gt;, compatible with &lt;em&gt;every&lt;/em&gt; browser, is a bit&amp;nbsp;worse:&lt;/p&gt;
&lt;table class="highlighttable"&gt;&lt;tr&gt;&lt;td class="linenos"&gt;&lt;div class="linenodiv"&gt;&lt;pre&gt;&lt;span class="normal"&gt; 1&lt;/span&gt;
&lt;span class="normal"&gt; 2&lt;/span&gt;
&lt;span class="normal"&gt; 3&lt;/span&gt;
&lt;span class="normal"&gt; 4&lt;/span&gt;
&lt;span class="normal"&gt; 5&lt;/span&gt;
&lt;span class="normal"&gt; 6&lt;/span&gt;
&lt;span class="normal"&gt; 7&lt;/span&gt;
&lt;span class="normal"&gt; 8&lt;/span&gt;
&lt;span class="normal"&gt; 9&lt;/span&gt;
&lt;span class="normal"&gt;10&lt;/span&gt;
&lt;span class="normal"&gt;11&lt;/span&gt;
&lt;span class="normal"&gt;12&lt;/span&gt;
&lt;span class="normal"&gt;13&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/td&gt;&lt;td class="code"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;clearfix&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="nd"&gt;after&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="k"&gt;visibility&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;hidden&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="k"&gt;display&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;block&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="k"&gt;font-size&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="k"&gt;content&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot; &amp;quot;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="k"&gt;clear&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;both&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="k"&gt;height&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;clearfix&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;display&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;inline-block&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="c"&gt;/* start commented backslash hack \*/&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;html&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;clearfix&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;height&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="kt"&gt;%&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;clearfix&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;display&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;block&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="c"&gt;/* close commented backslash hack */&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;p&gt;Is it any wonder folks started groaning about &lt;span class="caps"&gt;CSS&lt;/span&gt;?&lt;/p&gt;
&lt;p&gt;This was an era of blind copy/pasting in the frustrated hopes of making the damn thing work.  Case in point: someone (I dug the original source up once but can&amp;#8217;t find it now) had the bone-headed idea of always setting &lt;code&gt;body { font-size: 62.5% }&lt;/code&gt; due to a combination of &amp;#8220;relative units are good&amp;#8221; and wanting to override the seemingly massive default browser font size of 16px (which, it turns out, &lt;a href="https://www.smashingmagazine.com/2011/10/16-pixels-body-copy-anything-less-costly-mistake/"&gt;is correct&lt;/a&gt;) and dealing with &lt;span class="caps"&gt;IE&lt;/span&gt; bugs.  He walked it back a short time later, but the damage had been done, and now &lt;em&gt;thousands&lt;/em&gt; of websites start off that way as a &amp;#8220;best practice&amp;#8221;.  Which means if you want to change your browser&amp;#8217;s default font size in either direction, you&amp;#8217;re screwed — scale it down and a bunch of the Web becomes microscopic, scale it up and everything will still be much smaller than you&amp;#8217;ve asked for, scale it up more to compensate and everything that actually respects your decision will be ginormous.  At least we have better page zoom now, I&amp;nbsp;guess.&lt;/p&gt;
&lt;p&gt;Oh, and do remember: Stack Overflow didn&amp;#8217;t exist yet.  This stuff was passed around purely by word of mouth.  If you were lucky, you knew about some of the websites about websites, like &lt;a href="https://www.quirksmode.org/"&gt;quirks mode&lt;/a&gt; and &lt;a href="https://meyerweb.com/"&gt;Eric Meyer&amp;#8217;s website&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;In fact, check out Meyer&amp;#8217;s &lt;a href="https://meyerweb.com/eric/css/edge/index.html"&gt;css/edge&lt;/a&gt; site for some wild examples of stuff folks were doing, even with just &lt;span class="caps"&gt;CSS&lt;/span&gt; 1, as far back as 2002.  I still think &lt;a href="https://meyerweb.com/eric/css/edge/complexspiral/demo.html"&gt;complexspiral&lt;/a&gt; is pure genius, even though you could do it nowadays with &lt;code&gt;opacity&lt;/code&gt; and just one image.  The approach in &lt;a href="https://meyerweb.com/eric/css/edge/raggedfloat/demo.html"&gt;raggedfloat&lt;/a&gt; wouldn&amp;#8217;t get native support in &lt;span class="caps"&gt;CSS&lt;/span&gt; until a few years ago, with &lt;a href="https://developer.mozilla.org/en-US/docs/Web/CSS/shape-outside"&gt;&lt;code&gt;shape-outside&lt;/code&gt;&lt;/a&gt;!  He also brought us &lt;a href="https://meyerweb.com/eric/tools/css/reset/"&gt;&lt;span class="caps"&gt;CSS&lt;/span&gt; reset&lt;/a&gt;, eliminating differences between browsers&amp;#8217; default&amp;nbsp;styles.&lt;/p&gt;
&lt;p&gt;(I cannot understate how much of a &lt;span class="caps"&gt;CSS&lt;/span&gt; &lt;em&gt;pioneer&lt;/em&gt; Eric Meyer is.  When his young daughter Rebecca died six years ago, she was uniquely immortalized with her own &lt;span class="caps"&gt;CSS&lt;/span&gt; color name, &lt;a href="https://meyerweb.com/eric/thoughts/2014/06/19/rebeccapurple/"&gt;&lt;code&gt;rebeccapurple&lt;/code&gt;&lt;/a&gt;.  That&amp;#8217;s how highly the Web community thinks of him.  Also I have to go cry a bit over that story&amp;nbsp;now.)&lt;/p&gt;
&lt;h2 id="the-future-arrives-gradually"&gt;&lt;a class="toclink" href="#the-future-arrives-gradually"&gt;The future arrives,&amp;nbsp;gradually&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Designers and developers were pushing the bounds of what browsers were capable of.  Browsers were handling it all somewhat poorly.  All the fixes and workarounds and libraries were arcane, brittle, error-prone, and/or&amp;nbsp;heavy.&lt;/p&gt;
&lt;p&gt;Clearly, browsers needed some new functionality.  But just slopping something in wouldn&amp;#8217;t help; Microsoft had done plenty of that, and it had mostly made a&amp;nbsp;mess.&lt;/p&gt;
&lt;p&gt;Several struggling attempts began.  With the &lt;span class="caps"&gt;W3C&lt;/span&gt;&amp;#8217;s head still squarely up its own ass — even explicitly rejecting proposed enhancements to &lt;span class="caps"&gt;HTML&lt;/span&gt;, in favor of snorting &lt;span class="caps"&gt;XML&lt;/span&gt; — some folks from (active) browser vendors Apple, Mozilla, and Opera decided to make their own clubhouse.  &lt;span class="caps"&gt;WHATWG&lt;/span&gt; came into existence in June 2004, and they began work on &lt;span class="caps"&gt;HTML5&lt;/span&gt;.  (It would end up defining error-handling very explicitly, which completely obviated the need for &lt;span class="caps"&gt;XHTML&lt;/span&gt; and eliminated a number of security concerns when working with arbitrary &lt;span class="caps"&gt;HTML&lt;/span&gt;.  Also it gave us some new goodies, like native audio, video, and form controls for dates and colors and other stuff that had been clumsily handled by JavaScript-powered custom controls.  And, um, still often&amp;nbsp;are.)&lt;/p&gt;
&lt;p&gt;Then there was &lt;span class="caps"&gt;CSS&lt;/span&gt; 3.  I&amp;#8217;m not sure when it started to exist.  It emerged slowly, struggling, like a chick hatching from an egg and taking its damn sweet fucking time to actually get implemented&amp;nbsp;anywhere.&lt;/p&gt;
&lt;p&gt;I&amp;#8217;m having to do a lot of educated guessing here, but I &lt;em&gt;think&lt;/em&gt; it began with &lt;code&gt;border-radius&lt;/code&gt;.  Specifically, with &lt;code&gt;-moz-border-radius&lt;/code&gt;.  I don&amp;#8217;t know when it was first introduced, but the Mozilla bug tracker has mentions of it as far back as&amp;nbsp;1999.&lt;/p&gt;
&lt;p&gt;See, Firefox&amp;#8217;s own &lt;span class="caps"&gt;UI&lt;/span&gt; is rendered &lt;em&gt;with &lt;span class="caps"&gt;CSS&lt;/span&gt;&lt;/em&gt;.  If Mozilla wanted to do something that couldn&amp;#8217;t be done with &lt;span class="caps"&gt;CSS&lt;/span&gt;, they added a property of their own, prefixed with &lt;code&gt;-moz-&lt;/code&gt; to indicate it was their own invention.  And when there&amp;#8217;s no real harm in doing so, they leave the property accessible to websites as&amp;nbsp;well.&lt;/p&gt;
&lt;p&gt;My guess, then, is that the push for &lt;span class="caps"&gt;CSS&lt;/span&gt; 3 really began when Firefox took off and designers discovered &lt;code&gt;-moz-border-radius&lt;/code&gt;.  Suddenly, built-in rounded corners were available!  No more fucking around in Photoshop; you only needed to write a single line!  Practically overnight, everything everywhere had its corners filed&amp;nbsp;down.&lt;/p&gt;
&lt;p&gt;And from there, things snowballed.  Common problems were addressed one at a time by new &lt;span class="caps"&gt;CSS&lt;/span&gt; features, which were clustered together into a new &lt;span class="caps"&gt;CSS&lt;/span&gt; version: &lt;span class="caps"&gt;CSS&lt;/span&gt; 3.  The big ones were solutions to the design problems mentioned&amp;nbsp;before:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Rounded corners, provided by &lt;code&gt;border-radius&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Gradients, provided by &lt;code&gt;linear-gradient()&lt;/code&gt; and&amp;nbsp;friends.&lt;/li&gt;
&lt;li&gt;Multiple backgrounds, which weren&amp;#8217;t exactly a pressing concern, but which turned out to make some other stuff&amp;nbsp;easier.&lt;/li&gt;
&lt;li&gt;Translucency, provided by &lt;code&gt;opacity&lt;/code&gt; and colors with an alpha&amp;nbsp;channel.&lt;/li&gt;
&lt;li&gt;Box&amp;nbsp;shadows.&lt;/li&gt;
&lt;li&gt;Text shadows, which had been in &lt;span class="caps"&gt;CSS&lt;/span&gt; 2 but dropped in 2.1 and never implemented&amp;nbsp;anyway.&lt;/li&gt;
&lt;li&gt;Border images, so you could do even fancier things than mere rounded&amp;nbsp;borders.&lt;/li&gt;
&lt;li&gt;Transitions and animations, now doable with ease without needing jQuery (or any &lt;span class="caps"&gt;JS&lt;/span&gt; at&amp;nbsp;all).&lt;/li&gt;
&lt;li&gt;&lt;code&gt;:nth-child()&lt;/code&gt;, which solved the alternating rows problem with pure &lt;span class="caps"&gt;CSS&lt;/span&gt;.&lt;/li&gt;
&lt;li&gt;Transformations.  Wait, what?  This kinda leaked in from &lt;span class="caps"&gt;SVG&lt;/span&gt;, which browsers were also being expected to implement, and which is built heavily around transforms.  The code was already there, so, hey, now we can rotate stuff with &lt;span class="caps"&gt;CSS&lt;/span&gt;!  Couldn&amp;#8217;t do &lt;em&gt;that&lt;/em&gt; before.&amp;nbsp;Cool.&lt;/li&gt;
&lt;li&gt;Web fonts, which had been in &lt;span class="caps"&gt;CSS&lt;/span&gt; for some time but only ever implemented in &lt;span class="caps"&gt;IE&lt;/span&gt; and only with some goofy &lt;span class="caps"&gt;DRM&lt;/span&gt;-laden font format.  Now we weren&amp;#8217;t limited to the four bad fonts that ship with Windows and that no one else&amp;nbsp;has!&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;These were pretty great!  They didn&amp;#8217;t solve any layout problems, but they &lt;em&gt;did&lt;/em&gt; address aesthetic issues that designers had been clumsily working around by using loads of images and/or JavaScript.  That meant less stuff to download and more text used instead of images, both of which were pretty good for the&amp;nbsp;Web.&lt;/p&gt;
&lt;p&gt;The grand irony is that all the stuff you could do with these features went out of style almost immediately, and now we&amp;#8217;re back to flat rectangles&amp;nbsp;again.&lt;/p&gt;
&lt;h3 id="browser-prefixing-hell"&gt;&lt;a class="toclink" href="#browser-prefixing-hell"&gt;Browser prefixing&amp;nbsp;hell&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Alas!  All was still not right with the&amp;nbsp;world.&lt;/p&gt;
&lt;p&gt;Several of these new gizmos were, I believe, initially developed by browser vendors and prefixed.  Some later ones were designed by the &lt;span class="caps"&gt;CSS&lt;/span&gt; committee but implemented by browsers while the design was still in flux, and thus also&amp;nbsp;prefixed.&lt;/p&gt;
&lt;p&gt;So began &lt;em&gt;prefix hell&lt;/em&gt;, which continues to this&amp;nbsp;day.&lt;/p&gt;
&lt;p&gt;Mozilla had &lt;code&gt;-moz-border-radius&lt;/code&gt;, so when Safari implemented it, it was named &lt;code&gt;-webkit-border-radius&lt;/code&gt; (&amp;#8220;WebKit&amp;#8221; being the name of Apple&amp;#8217;s &lt;span class="caps"&gt;KHTML&lt;/span&gt; fork).  Then the &lt;span class="caps"&gt;CSS&lt;/span&gt; 3 spec standardized it and called it just &lt;code&gt;border-radius&lt;/code&gt;.  That meant that if you wanted to use rounded borders, you actually needed to give &lt;em&gt;three&lt;/em&gt;&amp;nbsp;rules:&lt;/p&gt;
&lt;table class="highlighttable"&gt;&lt;tr&gt;&lt;td class="linenos"&gt;&lt;div class="linenodiv"&gt;&lt;pre&gt;&lt;span class="normal"&gt;1&lt;/span&gt;
&lt;span class="normal"&gt;2&lt;/span&gt;
&lt;span class="normal"&gt;3&lt;/span&gt;
&lt;span class="normal"&gt;4&lt;/span&gt;
&lt;span class="normal"&gt;5&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/td&gt;&lt;td class="code"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="nt"&gt;element&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="kp"&gt;-moz-&lt;/span&gt;&lt;span class="k"&gt;border-radius&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="kt"&gt;em&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="kp"&gt;-webkit-&lt;/span&gt;&lt;span class="k"&gt;border-radius&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="kt"&gt;em&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;border-radius&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="kt"&gt;em&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;p&gt;The first two made the effect actually work in current browsers, and the last one was future-proofing: when browsers implemented the real rule and dropped the prefixed ones, it would take&amp;nbsp;over.&lt;/p&gt;
&lt;p&gt;You had to do this &lt;em&gt;every fucking time&lt;/em&gt;, since &lt;span class="caps"&gt;CSS&lt;/span&gt; isn&amp;#8217;t a programming language and has no macros or functions or the like.  Sometimes Opera and &lt;span class="caps"&gt;IE&lt;/span&gt; would have their own implementations with &lt;code&gt;-o-&lt;/code&gt; and &lt;code&gt;-ms-&lt;/code&gt; prefixes, bringing the total to five copies.  It got much worse with gradients; the syntax went through a number of major incompatible revisions, so you couldn&amp;#8217;t even rely on copy/pasting and changing the property&amp;nbsp;name!&lt;/p&gt;
&lt;p&gt;And plenty of folks, well, fucked it up.  I can&amp;#8217;t blame them too much; I mean, this sucks.  But enough pages used &lt;em&gt;only&lt;/em&gt; the prefixed forms, and not the final form, that browsers had to keep supporting the prefixed form for longer than they would&amp;#8217;ve liked to avoid breaking stuff.  And if the prefixed form still works and it&amp;#8217;s what you&amp;#8217;re used to writing, then maybe you still won&amp;#8217;t bother with the unprefixed&amp;nbsp;one.&lt;/p&gt;
&lt;p&gt;Worse, &lt;em&gt;some&lt;/em&gt; people would &lt;em&gt;only&lt;/em&gt; use the form that worked in their pet choice of browser.  This got especially bad with the rise of mobile web browsers.  The built-in browsers on iOS and Android are Safari (WebKit) and Chrome (originally WebKit, now a fork), so you only &amp;#8220;needed&amp;#8221; to use the &lt;code&gt;-webkit-&lt;/code&gt; properties.  Which made things difficult for Mozilla when it released &lt;a href="https://www.mozilla.org/en-US/firefox/mobile/"&gt;Firefox for Android&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Hey, remember that whole debacle with &lt;span class="caps"&gt;IE&lt;/span&gt; 6?  Here we are again!  It was bad enough that Mozilla eventually decided to &lt;a href="https://developer.mozilla.org/en-US/docs/Web/CSS/WebKit_Extensions#Supported_in_Firefox_with_-webkit-_prefix"&gt;implement&lt;/a&gt; a number of &lt;code&gt;-webkit-&lt;/code&gt; properties, which remain supported even in desktop Firefox to this day.  The situation is goofy enough that Firefox now supports some effects &lt;em&gt;only&lt;/em&gt; via these properties, like &lt;a href="https://developer.mozilla.org/en-US/docs/Web/CSS/-webkit-text-stroke"&gt;&lt;code&gt;-webkit-text-stroke&lt;/code&gt;&lt;/a&gt;, which isn&amp;#8217;t being&amp;nbsp;standardized.&lt;/p&gt;
&lt;p&gt;Even better, Chrome&amp;#8217;s current forked engine is called Blink, so &lt;em&gt;technically&lt;/em&gt; it shouldn&amp;#8217;t be using &lt;code&gt;-webkit-&lt;/code&gt; properties either.  And yet, here we are.  At least it&amp;#8217;s not as bad as the &lt;a href="https://webaim.org/blog/user-agent-string-history/"&gt;user agent string mess&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Browser vendors have pretty much abandoned prefixing, now; instead they hide experimental features behind flags (so they&amp;#8217;ll only work on the developer&amp;#8217;s machine), and new features are theoretically designed to be smaller and easier to&amp;nbsp;stabilize.&lt;/p&gt;
&lt;p&gt;This mess was probably a huge motivating factor for the development of &lt;a href="https://sass-lang.com/"&gt;Sass&lt;/a&gt; and &lt;a href="http://lesscss.org/"&gt;&lt;span class="caps"&gt;LESS&lt;/span&gt;&lt;/a&gt;, two languages that produce &lt;span class="caps"&gt;CSS&lt;/span&gt;.  Or&amp;#8230;  two &lt;span class="caps"&gt;CSS&lt;/span&gt; preprocessors, maybe.  They have very similar goals: both add variables, functions, and some form of macros to &lt;span class="caps"&gt;CSS&lt;/span&gt;, allowing you to eliminate a lot of the repetition and browser hacks and other nonsense from your stylesheets.  Hell, this blog &lt;a href="https://github.com/eevee/eev.ee/tree/988fc2b4547ee41388f29c4bad622c492c4c6f77/theme/static/sass"&gt;still uses &lt;span class="caps"&gt;SCSS&lt;/span&gt;&lt;/a&gt;, though its use has gradually decreased over&amp;nbsp;time.&lt;/p&gt;
&lt;h3 id="flexbox"&gt;&lt;a class="toclink" href="#flexbox"&gt;Flexbox&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;But then, like an angel descending from heaven&amp;#8230;  &lt;a href="https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_Flexible_Box_Layout"&gt;flexbox&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Flexbox has been around for a &lt;em&gt;long&lt;/em&gt; time — &lt;a href="https://www.caniuse.com/#feat=flexbox"&gt;allegedly&lt;/a&gt; it had partial support in Firefox 2, back in 2006!  It went through several incompatible revisions and took ages to stabilize.  Then &lt;span class="caps"&gt;IE&lt;/span&gt; took ages to implement it, and you don&amp;#8217;t really want to rely on layout tools that only work for half your audience.  It&amp;#8217;s only relatively recently (2015?  Later?) that flexbox has had sufficiently broad support to use safely.  And I could swear I still run into folks whose current Safari doesn&amp;#8217;t recognize it at all without prefixing, even though Safari supposedly dropped the prefixes five years&amp;nbsp;ago&amp;#8230;&lt;/p&gt;
&lt;p&gt;Anyway, flexbox is a &lt;span class="caps"&gt;CSS&lt;/span&gt; implementation of a pretty common &lt;span class="caps"&gt;GUI&lt;/span&gt; layout tool: you have a parent with some children, and the parent has some amount of space available, and it gets divided automatically between the children.  You know, it &lt;em&gt;puts things next to each other&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;The general idea is that the browser computes how much space the parent has available and the &amp;#8220;initial size&amp;#8221; of each child, figures out how much extra space there is, and distributes it according to the flexibleness of each child.  Think of a toolbar: you might want each button to have a fixed size (a flex of 0), but want to add spacers that share any leftover space equally, so you&amp;#8217;d give them a flex of&amp;nbsp;1.&lt;/p&gt;
&lt;p&gt;Once that&amp;#8217;s done, you have a number of quality-of-life options at your disposal, too: you can distribute the extra space &lt;em&gt;between&lt;/em&gt; the children instead, you can tell the children to stretch to the same height or align them in various ways, and you can even have them wrap into multiple rows if they won&amp;#8217;t all&amp;nbsp;fit!&lt;/p&gt;
&lt;p&gt;With this, we can take yet another crack at that &lt;a href="https://eev.ee/media/2020-02-css/thumbnail-grids.html#flexbox"&gt;thumbnail grid&lt;/a&gt;:&lt;/p&gt;
&lt;table class="highlighttable"&gt;&lt;tr&gt;&lt;td class="linenos"&gt;&lt;div class="linenodiv"&gt;&lt;pre&gt;&lt;span class="normal"&gt;1&lt;/span&gt;
&lt;span class="normal"&gt;2&lt;/span&gt;
&lt;span class="normal"&gt;3&lt;/span&gt;
&lt;span class="normal"&gt;4&lt;/span&gt;
&lt;span class="normal"&gt;5&lt;/span&gt;
&lt;span class="normal"&gt;6&lt;/span&gt;
&lt;span class="normal"&gt;7&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/td&gt;&lt;td class="code"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;thumbnail-grid&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;display&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;flex&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;flex-wrap&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;wrap&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;thumbnail-grid&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;li&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;flex&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;250&lt;/span&gt;&lt;span class="kt"&gt;px&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;p&gt;This is miraculous.  I forgot all about &lt;code&gt;inline-block&lt;/code&gt; overnight and mostly salivated over this until it was universally supported.  It even expresses very clearly what I&amp;nbsp;want.&lt;/p&gt;
&lt;p&gt;&amp;#8230;almost.  It still has the problem that too-wide cells will break the grid, since it&amp;#8217;s &lt;em&gt;still&lt;/em&gt; a horizontal row wrapped onto several independent lines.  It&amp;#8217;s pretty damn cool, though, and solves a number of other layout problems.  Surely this is good enough.&amp;nbsp;Unless&amp;#8230;?&lt;/p&gt;
&lt;p&gt;I&amp;#8217;d say mass adoption of flexbox marked the beginning of the modern era of &lt;span class="caps"&gt;CSS&lt;/span&gt;.  But there was one lingering&amp;nbsp;problem&amp;#8230;&lt;/p&gt;
&lt;h3 id="the-slow-agonizing-death-of-ie"&gt;&lt;a class="toclink" href="#the-slow-agonizing-death-of-ie"&gt;The slow, agonizing death of&amp;nbsp;IE&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;&lt;span class="caps"&gt;IE&lt;/span&gt; 6 took a long, long, &lt;em&gt;long&lt;/em&gt; time to go away.  It didn&amp;#8217;t drop below 10% market share (still a huge chunk) until early 2010 or&amp;nbsp;so.&lt;/p&gt;
&lt;p&gt;Firefox hit 1.0 at the end of 2004.  &lt;span class="caps"&gt;IE&lt;/span&gt; 7 wasn&amp;#8217;t released until two years later, it offered only modest improvements, it suffered from compatibility problems with stuff built for &lt;span class="caps"&gt;IE&lt;/span&gt; 6, and the &lt;span class="caps"&gt;IE&lt;/span&gt; 6 holdouts (many of whom were not Computer People) generally saw no reason to upgrade.  Vista shipped with &lt;span class="caps"&gt;IE&lt;/span&gt; 7, but Vista was kind of a flop — I don&amp;#8217;t believe it ever came close to overtaking &lt;span class="caps"&gt;XP&lt;/span&gt;, not in its entire&amp;nbsp;lifetime.&lt;/p&gt;
&lt;p&gt;Other factors included corporate &lt;span class="caps"&gt;IT&lt;/span&gt; policies, which often take the form of &amp;#8220;never upgrade anything ever&amp;#8221; — and often for good reason, as I heard endless tales of internal apps that only worked in &lt;span class="caps"&gt;IE&lt;/span&gt; 6 for all manner of horrifying reasons.  Then there was the &lt;em&gt;entirety of South Korea&lt;/em&gt;, which was &lt;em&gt;legally required&lt;/em&gt; to use &lt;span class="caps"&gt;IE&lt;/span&gt; 6 because they&amp;#8217;d enshrined in law some &lt;a href="https://www.washingtonpost.com/world/asia_pacific/due-to-security-law-south-korea-is-stuck-with-internet-explorer-for-online-shopping/2013/11/03/ffd2528a-3eff-11e3-b028-de922d7a3f47_story.html"&gt;security requirements&lt;/a&gt; that could only be implemented with an &lt;span class="caps"&gt;IE&lt;/span&gt; 6 ActiveX&amp;nbsp;control.&lt;/p&gt;
&lt;p&gt;So if you maintained a website that was used — or worse, &lt;em&gt;required&lt;/em&gt; — by people who worked for businesses or lived in other countries, you were pretty much stuck supporting &lt;span class="caps"&gt;IE&lt;/span&gt; 6.  Folks making little personal tools and websites abandoned &lt;span class="caps"&gt;IE&lt;/span&gt; 6 compatibility early on and plastered their sites with increasingly obnoxious banners taunting anyone who dared show up using it&amp;#8230;  but if you were someone&amp;#8217;s boss, why would you tell them it&amp;#8217;s okay to drop 20% of your potential audience?  Just work&amp;nbsp;harder!&lt;/p&gt;
&lt;p&gt;The tension grew over the years, as &lt;span class="caps"&gt;CSS&lt;/span&gt; became more capable and &lt;span class="caps"&gt;IE&lt;/span&gt; 6 remained an anchor.  It still didn&amp;#8217;t even understand &lt;em&gt;&lt;span class="caps"&gt;PNG&lt;/span&gt; alpha&lt;/em&gt; without workarounds, and meanwhile we were starting to get more critical features like native video in &lt;span class="caps"&gt;HTML5&lt;/span&gt;.  The workarounds grew messier, and the list of features you basically just couldn&amp;#8217;t use grew longer.  (I&amp;#8217;d show you what my blog looks like in &lt;span class="caps"&gt;IE&lt;/span&gt; 6, but I don&amp;#8217;t think it can even connect — the &lt;span class="caps"&gt;TLS&lt;/span&gt; stuff it supports is so ancient and broken that it&amp;#8217;s been disabled on most&amp;nbsp;servers!)&lt;/p&gt;
&lt;p&gt;Shoutouts, by the way, to some folks on the YouTube team, who in July 2009 &lt;a href="https://www.theverge.com/2019/5/4/18529381/google-youtube-internet-explorer-6-kill-plot-engineer"&gt;added a warning banner&lt;/a&gt; imploring &lt;span class="caps"&gt;IE&lt;/span&gt; 6 users to switch to &lt;em&gt;anything&lt;/em&gt; else — without asking anyone for approval.  &amp;#8220;Within one month&amp;#8230;  over 10 percent of global &lt;span class="caps"&gt;IE6&lt;/span&gt; traffic had dropped off.&amp;#8221;  Not all heroes wear&amp;nbsp;capes.&lt;/p&gt;
&lt;p&gt;I&amp;#8217;d mark the beginning of the end as the day YouTube &lt;em&gt;actually&lt;/em&gt; dropped &lt;span class="caps"&gt;IE&lt;/span&gt; 6 support — March 13, 2010, almost nine years after its release.  I don&amp;#8217;t know how much of a &lt;em&gt;direct&lt;/em&gt; impact YouTube has on corporate users or the South Korean government, but a massive web company dropping an entire browser sends a pretty strong&amp;nbsp;message.&lt;/p&gt;
&lt;p&gt;There were other versions of &lt;span class="caps"&gt;IE&lt;/span&gt;, of course, and many of them were messy headaches in their own right.  But each subsequent one became less of a pain, and nowadays you don&amp;#8217;t even have to think too much about testing in &lt;span class="caps"&gt;IE&lt;/span&gt; (now Edge).  Just in time for Microsoft to scrap their own rendering engine and turn their browser into a Chrome&amp;nbsp;clone.&lt;/p&gt;
&lt;h2 id="now"&gt;&lt;a class="toclink" href="#now"&gt;Now&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;&lt;span class="caps"&gt;CSS&lt;/span&gt; is pretty great now.  You don&amp;#8217;t need weird fucking hacks just to put things next to each other.  Browser dev tools are built in, now, and are fucking amazing — Firefox has started specifically warning you when some &lt;span class="caps"&gt;CSS&lt;/span&gt; properties won&amp;#8217;t take effect because of the values of others!  Obscure implicit side effects like &amp;#8220;stacking contexts&amp;#8221; (whatever those are) can now be set explicitly, with properties like &lt;code&gt;isolation: isolate&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;In fact, let me just list everything that I can think of that you can do in &lt;span class="caps"&gt;CSS&lt;/span&gt; now.  This isn&amp;#8217;t a guide to all possible uses of styling, but if your &lt;span class="caps"&gt;CSS&lt;/span&gt; knowledge hasn&amp;#8217;t been updated since 2008, I hope this whets your appetite.  And this stuff is just &lt;span class="caps"&gt;CSS&lt;/span&gt;!  So many things that used to be impossible or painful or require clumsy plugins are now natively supported — audio, video, custom drawing, 3D rendering&amp;#8230;  not to mention the vast ergonomic improvements to&amp;nbsp;JavaScript.&lt;/p&gt;
&lt;h3 id="layout"&gt;&lt;a class="toclink" href="#layout"&gt;Layout&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;A &lt;a href="https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_Grid_Layout"&gt;grid&lt;/a&gt; container can do pretty much anything tables can do, and more, including automatically determining how many columns will fit.  It&amp;#8217;s fucking amazing.  More on that&amp;nbsp;below.&lt;/p&gt;
&lt;p&gt;A &lt;a href="https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_Flexible_Box_Layout"&gt;flexbox&lt;/a&gt; container lays out its children in a row or column, allowing each child to declare its &amp;#8220;default&amp;#8221; size and what proportion of leftover space it wants to consume.  Flexboxes can wrap, rearrange children without changing source order, and align children in a number of&amp;nbsp;ways.&lt;/p&gt;
&lt;p&gt;&lt;a href="https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_Columns"&gt;Columns&lt;/a&gt; will pour text into, well, multiple&amp;nbsp;columns.&lt;/p&gt;
&lt;p&gt;The &lt;a href="https://developer.mozilla.org/en-US/docs/Web/CSS/box-sizing"&gt;&lt;code&gt;box-sizing&lt;/code&gt;&lt;/a&gt; property lets you opt into the &lt;span class="caps"&gt;IE&lt;/span&gt; box model on a per-element basis, for when you need an entire element to take up a fixed amount of space and need padding/borders to &lt;em&gt;subtract&lt;/em&gt; from&amp;nbsp;that.&lt;/p&gt;
&lt;p&gt;&lt;a href="https://developer.mozilla.org/en-US/docs/Web/CSS/display"&gt;&lt;code&gt;display: contents&lt;/code&gt;&lt;/a&gt; dumps an element&amp;#8217;s contents out into its parent, as if it weren&amp;#8217;t there at all.  &lt;code&gt;display: flow-root&lt;/code&gt; is basically an automatic clearfix, only a decade too&amp;nbsp;late.&lt;/p&gt;
&lt;p&gt;&lt;a href="https://developer.mozilla.org/en-US/docs/Web/CSS/width"&gt;&lt;code&gt;width&lt;/code&gt;&lt;/a&gt; can now be set to &lt;code&gt;min-content&lt;/code&gt;, &lt;code&gt;max-content&lt;/code&gt;, or the &lt;code&gt;fit-content()&lt;/code&gt; function for more flexible&amp;nbsp;behavior.&lt;/p&gt;
&lt;p&gt;&lt;a href="https://developer.mozilla.org/en-US/docs/Web/CSS/white-space"&gt;&lt;code&gt;white-space: pre-wrap&lt;/code&gt;&lt;/a&gt; preserves whitespace, but breaks lines where necessary to avoid overflow.  Also useful is &lt;code&gt;pre-line&lt;/code&gt;, which collapses sequences of spaces down to a single space, but preserves literal&amp;nbsp;newlines.&lt;/p&gt;
&lt;p&gt;&lt;a href="https://developer.mozilla.org/en-US/docs/Web/CSS/text-overflow"&gt;&lt;code&gt;text-overflow&lt;/code&gt;&lt;/a&gt; cuts off overflowing text with an ellipsis (or custom character) when it would overflow, rather than simply truncating it.  Also specced is the ability to fade out the text, but this is as yet&amp;nbsp;unimplemented.&lt;/p&gt;
&lt;p&gt;&lt;a href="https://developer.mozilla.org/en-US/docs/Web/CSS/shape-outside"&gt;&lt;code&gt;shape-outside&lt;/code&gt;&lt;/a&gt; alters the shape used when wrapping text around a float.  It can even use the alpha channel of an image as the&amp;nbsp;shape.&lt;/p&gt;
&lt;p&gt;&lt;a href="https://developer.mozilla.org/en-US/docs/Web/CSS/resize"&gt;&lt;code&gt;resize&lt;/code&gt;&lt;/a&gt; gives an arbitrary element a resize handle (as long as it has &lt;code&gt;overflow&lt;/code&gt;).&lt;/p&gt;
&lt;p&gt;&lt;a href="https://developer.mozilla.org/en-US/docs/Web/CSS/writing-mode"&gt;&lt;code&gt;writing-mode&lt;/code&gt;&lt;/a&gt; sets the direction that text flows.  If your design needs to work for multiple writing modes, a number of &lt;span class="caps"&gt;CSS&lt;/span&gt; properties that mention left/right/top/bottom have alternatives that describe directions in terms of the writing mode: &lt;a href="https://developer.mozilla.org/en-US/docs/Web/CSS/inset-block"&gt;&lt;code&gt;inset-block&lt;/code&gt;&lt;/a&gt; and &lt;a href="https://developer.mozilla.org/en-US/docs/Web/CSS/inset-inline"&gt;&lt;code&gt;inset-inline&lt;/code&gt;&lt;/a&gt; for position, &lt;a href="https://developer.mozilla.org/en-US/docs/Web/CSS/block-size"&gt;&lt;code&gt;block-size&lt;/code&gt;&lt;/a&gt; and &lt;a href="https://developer.mozilla.org/en-US/docs/Web/CSS/inline-size"&gt;&lt;code&gt;inline-size&lt;/code&gt;&lt;/a&gt; for width/height, &lt;a href="https://developer.mozilla.org/en-US/docs/Web/CSS/border-block"&gt;&lt;code&gt;border-block&lt;/code&gt;&lt;/a&gt; and &lt;a href="https://developer.mozilla.org/en-US/docs/Web/CSS/border-inline"&gt;&lt;code&gt;border-inline&lt;/code&gt;&lt;/a&gt; for borders, and similar for padding and&amp;nbsp;margins.&lt;/p&gt;
&lt;h3 id="aesthetics"&gt;&lt;a class="toclink" href="#aesthetics"&gt;Aesthetics&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;&lt;a href="https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_Transitions"&gt;Transitions&lt;/a&gt; smoothly interpolate a value whenever it changes, whether due to an effect like &lt;code&gt;:hover&lt;/code&gt; or e.g. a class being added from JavaScript.  &lt;a href="https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_Animations"&gt;Animations&lt;/a&gt; are similar, but play a predefined animation automatically.  Both can use a number of different &lt;a href="https://easings.net/en"&gt;easing functions&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;&lt;a href="https://developer.mozilla.org/en-US/docs/Web/CSS/border-radius"&gt;&lt;code&gt;border-radius&lt;/code&gt;&lt;/a&gt; rounds off the corners of a box.  The corners can all be different sizes, and can be circular or elliptical.  The curve also applies to the border, background, and any box&amp;nbsp;shadows.&lt;/p&gt;
&lt;p&gt;&lt;a href="https://developer.mozilla.org/en-US/docs/Web/CSS/box-shadow"&gt;Box shadows&lt;/a&gt; can be used for the obvious effect of casting a drop shadow.  You can also use multiple shadows and &lt;code&gt;inset&lt;/code&gt; shadows for a variety of clever&amp;nbsp;effects.&lt;/p&gt;
&lt;p&gt;&lt;a href="https://developer.mozilla.org/en-US/docs/Web/CSS/text-shadow"&gt;&lt;code&gt;text-shadow&lt;/code&gt;&lt;/a&gt; does what it says on the tin, though you can also stack several of them for a rough approximation of a text&amp;nbsp;outline.&lt;/p&gt;
&lt;p&gt;&lt;a href="https://developer.mozilla.org/en-US/docs/Web/CSS/transform"&gt;&lt;code&gt;transform&lt;/code&gt;&lt;/a&gt; lets you apply an arbitrary matrix transformation to an element — that is, you can scale, rotate, skew, translate, and/or do perspective transform, all without affecting&amp;nbsp;layout.&lt;/p&gt;
&lt;p&gt;&lt;a href="https://developer.mozilla.org/en-US/docs/Web/CSS/filter"&gt;&lt;code&gt;filter&lt;/code&gt;&lt;/a&gt; (distinct from the &lt;span class="caps"&gt;IE&lt;/span&gt; 6 one) offers a handful of specific visual filters you can apply to an element.  Most of them affect color, but there&amp;#8217;s also a &lt;code&gt;blur()&lt;/code&gt; and a &lt;code&gt;drop-shadow()&lt;/code&gt; (which, unlike &lt;code&gt;box-shadow&lt;/code&gt;, applies to an element&amp;#8217;s appearance rather than its containing&amp;nbsp;box).&lt;/p&gt;
&lt;p&gt;&lt;a href="https://developer.mozilla.org/en-US/docs/Web/CSS/linear-gradient"&gt;&lt;code&gt;linear-gradient()&lt;/code&gt;&lt;/a&gt;, &lt;a href="https://developer.mozilla.org/en-US/docs/Web/CSS/radial-gradient"&gt;&lt;code&gt;radial-gradient()&lt;/code&gt;&lt;/a&gt;, the new and less-supported &lt;a href="https://developer.mozilla.org/en-US/docs/Web/CSS/conic-gradient"&gt;&lt;code&gt;conic-gradient()&lt;/code&gt;&lt;/a&gt;, and their &lt;code&gt;repeating-*&lt;/code&gt; variants all produce gradient images and can be used anywhere in &lt;span class="caps"&gt;CSS&lt;/span&gt; that an image is expected, most commonly as a &lt;code&gt;background-image&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;&lt;a href="https://developer.mozilla.org/en-US/docs/Web/CSS/scrollbar-color"&gt;&lt;code&gt;scrollbar-color&lt;/code&gt;&lt;/a&gt; changes the scrollbar color, with the downside of reducing the scrollbar to a very simple thumb-and-track in current&amp;nbsp;browsers.&lt;/p&gt;
&lt;p&gt;&lt;a href="https://developer.mozilla.org/en-US/docs/Web/CSS/background-size"&gt;&lt;code&gt;background-size: cover&lt;/code&gt; and &lt;code&gt;contain&lt;/code&gt;&lt;/a&gt; will scale a background image proportionally, either big enough to completely cover the element (even if cropped) or small enough to exactly fit inside it (even if it doesn&amp;#8217;t cover the entire&amp;nbsp;background).&lt;/p&gt;
&lt;p&gt;&lt;a href="https://developer.mozilla.org/en-US/docs/Web/CSS/object-fit"&gt;&lt;code&gt;object-fit&lt;/code&gt;&lt;/a&gt; is a similar idea but for non-background media, like &lt;code&gt;&amp;lt;img&amp;gt;&lt;/code&gt;s.  The related &lt;a href="https://developer.mozilla.org/en-US/docs/Web/CSS/object-position"&gt;&lt;code&gt;object-position&lt;/code&gt;&lt;/a&gt; is like &lt;code&gt;background-position&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Multiple backgrounds are possible, which is especially useful with gradients — you can stack multiple gradients, other background images, and a solid color on the&amp;nbsp;bottom.&lt;/p&gt;
&lt;p&gt;&lt;a href="https://developer.mozilla.org/en-US/docs/Web/CSS/text-decoration"&gt;&lt;code&gt;text-decoration&lt;/code&gt;&lt;/a&gt; is fancier than it used to be; you can now set the color of the line and use several different kinds of lines, including dashed, dotted, and&amp;nbsp;wavy.&lt;/p&gt;
&lt;p&gt;&lt;a href="https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_Lists_and_Counters/Using_CSS_counters"&gt;&lt;span class="caps"&gt;CSS&lt;/span&gt; counters&lt;/a&gt; can be used to number arbitrary elements in an arbitrary way, exposing the counting ability of &lt;code&gt;&amp;lt;ol&amp;gt;&lt;/code&gt; to any set of elements you&amp;nbsp;want.  &lt;/p&gt;
&lt;p&gt;The &lt;a href="https://developer.mozilla.org/en-US/docs/Web/CSS/::marker"&gt;&lt;code&gt;::marker&lt;/code&gt;&lt;/a&gt; pseudo-element allows you to style a list item&amp;#8217;s marker box, or even replace it outright with a custom counter.  Browser support is spotty, but improving.  Similarly, the &lt;a href="https://developer.mozilla.org/en-US/docs/Web/CSS/@counter-style"&gt;&lt;code&gt;@counter-style&lt;/code&gt;&lt;/a&gt; at-rule implements an entirely new counter style (like 1 2 3, i ii iii, A B C, etc.) which you can then use anywhere, though only Firefox supports it so&amp;nbsp;far.&lt;/p&gt;
&lt;p&gt;&lt;a href="https://developer.mozilla.org/en-US/docs/Web/CSS/image-set"&gt;&lt;code&gt;image-set()&lt;/code&gt;&lt;/a&gt; provides a list of candidate images and lets the browser choose the most appropriate one based on the pixel density of the user&amp;#8217;s&amp;nbsp;screen.&lt;/p&gt;
&lt;p&gt;&lt;a href="https://developer.mozilla.org/en-US/docs/Web/CSS/@font-face"&gt;&lt;code&gt;@font-face&lt;/code&gt;&lt;/a&gt; defines a font that can be downloaded, though you can avoid figuring out how to use it correctly by using &lt;a href="https://developers.google.com/fonts/"&gt;Google Fonts&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;&lt;a href="https://developer.mozilla.org/en-US/docs/Web/CSS/pointer-events"&gt;&lt;code&gt;pointer-events: none&lt;/code&gt;&lt;/a&gt; makes an element ignore the mouse entirely; it can&amp;#8217;t be hovered, and clicks will go straight through it to the element&amp;nbsp;below.&lt;/p&gt;
&lt;p&gt;&lt;a href="https://developer.mozilla.org/en-US/docs/Web/CSS/image-rendering"&gt;&lt;code&gt;image-rendering&lt;/code&gt;&lt;/a&gt; can force an image to be resized nearest-neighbor rather than interpolated, though browser support is still spotty and you may need to also include some vendor-specific&amp;nbsp;properties.&lt;/p&gt;
&lt;p&gt;&lt;a href="https://developer.mozilla.org/en-US/docs/Web/CSS/clip-path"&gt;&lt;code&gt;clip-path&lt;/code&gt;&lt;/a&gt; crops an element to an arbitrary shape.  There&amp;#8217;s also &lt;a href="https://developer.mozilla.org/en-US/docs/Web/CSS/mask"&gt;&lt;code&gt;mask&lt;/code&gt;&lt;/a&gt; for arbitrary alpha masking, but browser support is spotty and hoo boy is this one&amp;nbsp;complicated.&lt;/p&gt;
&lt;h3 id="syntax-and-misc"&gt;&lt;a class="toclink" href="#syntax-and-misc"&gt;Syntax and&amp;nbsp;misc&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;&lt;a href="https://developer.mozilla.org/en-US/docs/Web/CSS/@supports"&gt;&lt;code&gt;@supports&lt;/code&gt;&lt;/a&gt; lets you explicitly write different &lt;span class="caps"&gt;CSS&lt;/span&gt; depending on what the browser supports, though it&amp;#8217;s nowhere near as useful nowadays as it would&amp;#8217;ve been in&amp;nbsp;2004.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;A &amp;gt; B&lt;/code&gt; selects immediate children.  &lt;code&gt;A ~ B&lt;/code&gt; selects siblings.  &lt;code&gt;A + B&lt;/code&gt; selects immediate (element) siblings.  Square brackets can do a &lt;a href="https://developer.mozilla.org/en-US/docs/Web/CSS/Attribute_selectors"&gt;bunch of stuff&lt;/a&gt; to select based on attributes; most obvious is &lt;code&gt;input[type=checkbox]&lt;/code&gt;, though you can also do interesting things with matching parts of &lt;code&gt;&amp;lt;a href&amp;gt;&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;There are a whole bunch of pseudo-classes now.  Many of them are for form elements: &lt;a href="https://developer.mozilla.org/en-US/docs/Web/CSS/:enabled"&gt;&lt;code&gt;:enabled&lt;/code&gt;&lt;/a&gt; and &lt;a href="https://developer.mozilla.org/en-US/docs/Web/CSS/:disabled"&gt;&lt;code&gt;:disabled&lt;/code&gt;&lt;/a&gt;; &lt;a href="https://developer.mozilla.org/en-US/docs/Web/CSS/:checked"&gt;&lt;code&gt;:checked&lt;/code&gt;&lt;/a&gt; and &lt;a href="https://developer.mozilla.org/en-US/docs/Web/CSS/:indeterminate"&gt;&lt;code&gt;:indeterminate&lt;/code&gt;&lt;/a&gt; (also apply to radio and &lt;code&gt;&amp;lt;option&amp;gt;&lt;/code&gt;); &lt;a href="https://developer.mozilla.org/en-US/docs/Web/CSS/:required"&gt;&lt;code&gt;:required&lt;/code&gt;&lt;/a&gt; and &lt;a href="https://developer.mozilla.org/en-US/docs/Web/CSS/:optional"&gt;&lt;code&gt;:optional&lt;/code&gt;&lt;/a&gt;; &lt;a href="https://developer.mozilla.org/en-US/docs/Web/CSS/:read-write"&gt;&lt;code&gt;:read-write&lt;/code&gt;&lt;/a&gt; and &lt;a href="https://developer.mozilla.org/en-US/docs/Web/CSS/:read-only"&gt;&lt;code&gt;:read-only&lt;/code&gt;&lt;/a&gt;; &lt;a href="https://developer.mozilla.org/en-US/docs/Web/CSS/:in-range"&gt;&lt;code&gt;:in-range&lt;/code&gt;&lt;/a&gt;/&lt;a href="https://developer.mozilla.org/en-US/docs/Web/CSS/:out-of-range"&gt;&lt;code&gt;:out-of-range&lt;/code&gt;&lt;/a&gt; and &lt;a href="https://developer.mozilla.org/en-US/docs/Web/CSS/:valid"&gt;&lt;code&gt;:valid&lt;/code&gt;&lt;/a&gt;/&lt;a href="https://developer.mozilla.org/en-US/docs/Web/CSS/:invalid"&gt;&lt;code&gt;:invalid&lt;/code&gt;&lt;/a&gt; (for use with &lt;span class="caps"&gt;HTML5&lt;/span&gt; client-side form validation); &lt;a href="https://developer.mozilla.org/en-US/docs/Web/CSS/:focus"&gt;&lt;code&gt;:focus&lt;/code&gt;&lt;/a&gt; and &lt;a href="https://developer.mozilla.org/en-US/docs/Web/CSS/:focus-within"&gt;&lt;code&gt;:focus-within&lt;/code&gt;&lt;/a&gt;; and &lt;a href="https://developer.mozilla.org/en-US/docs/Web/CSS/:default"&gt;&lt;code&gt;:default&lt;/code&gt;&lt;/a&gt; (which selects the default form button and any pre-selected checkboxes, radio buttons, and &lt;code&gt;&amp;lt;option&amp;gt;&lt;/code&gt;s).&lt;/p&gt;
&lt;p&gt;For targeting specific elements within a set of siblings, we have: &lt;a href="https://developer.mozilla.org/en-US/docs/Web/CSS/:first-child"&gt;&lt;code&gt;:first-child&lt;/code&gt;&lt;/a&gt;, &lt;a href="https://developer.mozilla.org/en-US/docs/Web/CSS/:last-child"&gt;&lt;code&gt;:last-child&lt;/code&gt;&lt;/a&gt;, and &lt;a href="https://developer.mozilla.org/en-US/docs/Web/CSS/:only-child"&gt;&lt;code&gt;:only-child&lt;/code&gt;&lt;/a&gt;; &lt;a href="https://developer.mozilla.org/en-US/docs/Web/CSS/:first-of-type"&gt;&lt;code&gt;:first-of-type&lt;/code&gt;&lt;/a&gt;, &lt;a href="https://developer.mozilla.org/en-US/docs/Web/CSS/:last-of-type"&gt;&lt;code&gt;:last-of-type&lt;/code&gt;&lt;/a&gt;, and &lt;a href="https://developer.mozilla.org/en-US/docs/Web/CSS/:only-of-type"&gt;&lt;code&gt;:only-of-type&lt;/code&gt;&lt;/a&gt; (where &amp;#8220;type&amp;#8221; means tag name); and &lt;a href="https://developer.mozilla.org/en-US/docs/Web/CSS/:nth-child"&gt;&lt;code&gt;:nth-child()&lt;/code&gt;&lt;/a&gt;, &lt;a href="https://developer.mozilla.org/en-US/docs/Web/CSS/:nth-last-child"&gt;&lt;code&gt;:nth-last-child()&lt;/code&gt;&lt;/a&gt;, &lt;a href="https://developer.mozilla.org/en-US/docs/Web/CSS/:nth-of-type"&gt;&lt;code&gt;:nth-of-type()&lt;/code&gt;&lt;/a&gt;, and &lt;a href="https://developer.mozilla.org/en-US/docs/Web/CSS/:nth-last-of-type"&gt;&lt;code&gt;:nth-last-of-type()&lt;/code&gt;&lt;/a&gt; (to select every second, third, etc.&amp;nbsp;element).&lt;/p&gt;
&lt;p&gt;&lt;a href="https://developer.mozilla.org/en-US/docs/Web/CSS/:not"&gt;&lt;code&gt;:not()&lt;/code&gt;&lt;/a&gt; inverts a selector.  &lt;a href="https://developer.mozilla.org/en-US/docs/Web/CSS/:empty"&gt;&lt;code&gt;:empty&lt;/code&gt;&lt;/a&gt; selects elements with no children and no text.  &lt;a href="https://developer.mozilla.org/en-US/docs/Web/CSS/:target"&gt;&lt;code&gt;:target&lt;/code&gt;&lt;/a&gt; selects the element jumped to with a &lt;span class="caps"&gt;URL&lt;/span&gt; fragment (e.g. if the address bar shows &lt;code&gt;index.html#foo&lt;/code&gt;, this selects the element whose &lt;span class="caps"&gt;ID&lt;/span&gt; is &lt;code&gt;foo&lt;/code&gt;).&lt;/p&gt;
&lt;p&gt;&lt;a href="https://developer.mozilla.org/en-US/docs/Web/CSS/::before"&gt;&lt;code&gt;::before&lt;/code&gt;&lt;/a&gt; and &lt;a href="https://developer.mozilla.org/en-US/docs/Web/CSS/::after"&gt;&lt;code&gt;::after&lt;/code&gt;&lt;/a&gt; should have two colons now, to indicate that they create pseudo-elements rather than merely scoping the selector they&amp;#8217;re attached to.  &lt;a href="https://developer.mozilla.org/en-US/docs/Web/CSS/::selection"&gt;&lt;code&gt;::selection&lt;/code&gt;&lt;/a&gt; customizes how selected text appears; &lt;a href="https://developer.mozilla.org/en-US/docs/Web/CSS/::placeholder"&gt;&lt;code&gt;::placeholder&lt;/code&gt;&lt;/a&gt; customizes how placeholder text (in text fields)&amp;nbsp;appears.&lt;/p&gt;
&lt;p&gt;&lt;a href="https://developer.mozilla.org/en-US/docs/Web/CSS/@media"&gt;Media queries&lt;/a&gt; do just a whole bunch of stuff so your page can adapt based on how it&amp;#8217;s being viewed.  The &lt;a href="https://developer.mozilla.org/en-US/docs/Web/CSS/@media/prefers-color-scheme"&gt;&lt;code&gt;prefers-color-scheme&lt;/code&gt;&lt;/a&gt; media query tells you if the user&amp;#8217;s system is set to a light or dark theme, so you can adjust accordingly without having to&amp;nbsp;ask.&lt;/p&gt;
&lt;p&gt;You can write translucent &lt;a href="https://developer.mozilla.org/en-US/docs/Web/CSS/color_value"&gt;colors&lt;/a&gt; as &lt;code&gt;#rrggbbaa&lt;/code&gt; or &lt;code&gt;#rgba&lt;/code&gt;, as well as using the &lt;code&gt;rgba()&lt;/code&gt; and &lt;code&gt;hsla()&lt;/code&gt; functions.&lt;/p&gt;
&lt;p&gt;&lt;a href="https://developer.mozilla.org/en-US/docs/Web/CSS/angle"&gt;Angles&lt;/a&gt; can be described as fractions of a full circle with the &lt;code&gt;turn&lt;/code&gt; unit.  Of course, &lt;code&gt;deg&lt;/code&gt; and &lt;code&gt;rad&lt;/code&gt; (and &lt;code&gt;grad&lt;/code&gt;) are also&amp;nbsp;available.&lt;/p&gt;
&lt;p&gt;&lt;a href="https://developer.mozilla.org/en-US/docs/Web/CSS/Using_CSS_custom_properties"&gt;&lt;span class="caps"&gt;CSS&lt;/span&gt; variables&lt;/a&gt; (officially, &amp;#8220;custom properties&amp;#8221;) let you specify arbitrary named values that can be used anywhere a value would appear.  You can use this to reduce the amount of &lt;span class="caps"&gt;CSS&lt;/span&gt; fiddling needs doing in JavaScript (e.g., recolor a complex part of a page by setting a &lt;span class="caps"&gt;CSS&lt;/span&gt; variable instead of manually adjusting a number of properties), or have a generic component that reacts to variables set by an&amp;nbsp;ancestor.&lt;/p&gt;
&lt;p&gt;&lt;a href="https://developer.mozilla.org/en-US/docs/Web/CSS/calc"&gt;&lt;code&gt;calc()&lt;/code&gt;&lt;/a&gt; computes an arbitrary expression and updates automatically (though it&amp;#8217;s somewhat obviated by &lt;code&gt;box-sizing&lt;/code&gt;).&lt;/p&gt;
&lt;p&gt;The &lt;a href="https://developer.mozilla.org/en-US/docs/Web/CSS/length"&gt;&lt;code&gt;vw&lt;/code&gt;, &lt;code&gt;vh&lt;/code&gt;, &lt;code&gt;vmin&lt;/code&gt;, and &lt;code&gt;vmax&lt;/code&gt; units&lt;/a&gt; let you specify lengths as a fraction of the viewport&amp;#8217;s width or height, or whichever of the two is&amp;nbsp;bigger/smaller.&lt;/p&gt;
&lt;hr /&gt;
&lt;p&gt;Phew!  I&amp;#8217;m sure I&amp;#8217;m forgetting plenty and folks will have even longer lists of interesting tidbits in the comments.  Thanks for saving me some effort!  Now I can stop browsing &lt;span class="caps"&gt;MDN&lt;/span&gt; and do this final fun&amp;nbsp;part.&lt;/p&gt;
&lt;h3 id="state-of-the-art-thumbnail-grid"&gt;&lt;a class="toclink" href="#state-of-the-art-thumbnail-grid"&gt;State of the art thumbnail&amp;nbsp;grid&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;At long last, we arrive at the final and objectively correct way to construct a thumbnail grid: using &lt;a href="https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_Grid_Layout"&gt;&lt;span class="caps"&gt;CSS&lt;/span&gt; grid&lt;/a&gt;.  You can tell this is the right thing to use because it has &amp;#8220;grid&amp;#8221; in the name.  Modern &lt;span class="caps"&gt;CSS&lt;/span&gt; features are pretty great about letting you say the thing you want and having it happen, rather than trying to coax it into happening implicitly via&amp;nbsp;voodoo.&lt;/p&gt;
&lt;p&gt;And it is oh so&amp;nbsp;simple:&lt;/p&gt;
&lt;table class="highlighttable"&gt;&lt;tr&gt;&lt;td class="linenos"&gt;&lt;div class="linenodiv"&gt;&lt;pre&gt;&lt;span class="normal"&gt;1&lt;/span&gt;
&lt;span class="normal"&gt;2&lt;/span&gt;
&lt;span class="normal"&gt;3&lt;/span&gt;
&lt;span class="normal"&gt;4&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/td&gt;&lt;td class="code"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;thumbnail-grid&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;display&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;grid&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;grid&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;auto&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="k"&gt;flow&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;repeat&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;auto&lt;/span&gt;&lt;span class="nv"&gt;-fit&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;minmax&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;250&lt;/span&gt;&lt;span class="kt"&gt;px&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="n"&gt;fr&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;p&gt;Done!  That &lt;a href="https://eev.ee/media/2020-02-css/thumbnail-grids.html#grid"&gt;gives you a grid&lt;/a&gt;.  You have myriad other twiddles to play with, just as with flexbox, but that&amp;#8217;s the basic idea.  You don&amp;#8217;t even need to style the elements themselves; most of the layout work is done in the&amp;nbsp;container.&lt;/p&gt;
&lt;p&gt;The &lt;a href="https://developer.mozilla.org/en-US/docs/Web/CSS/grid"&gt;&lt;code&gt;grid&lt;/code&gt; shorthand property&lt;/a&gt; looks a little intimidating, but only because it&amp;#8217;s so flexible.  It&amp;#8217;s saying: fill the grid one row at a time, generating as many rows as necessary; make as many 250px columns as will fit, and share any leftover space between them&amp;nbsp;equally.&lt;/p&gt;
&lt;p&gt;&lt;span class="caps"&gt;CSS&lt;/span&gt; grids are also handy for laying out &lt;code&gt;&amp;lt;dl&amp;gt;&lt;/code&gt;s, something that&amp;#8217;s historically been a massive pain to make work — a &lt;code&gt;&amp;lt;dl&amp;gt;&lt;/code&gt; contains any number of &lt;code&gt;&amp;lt;dt&amp;gt;&lt;/code&gt;s followed by any number of &lt;code&gt;&amp;lt;dd&amp;gt;&lt;/code&gt;s (including zero), and the only way to style this until grid was to float the &lt;code&gt;&amp;lt;dt&amp;gt;&lt;/code&gt;s, which meant they had to have a fixed width.  Now you can just tell the &lt;code&gt;&amp;lt;dt&amp;gt;&lt;/code&gt;s to go in the first column and &lt;code&gt;&amp;lt;dd&amp;gt;&lt;/code&gt;s to go in the second, and grid will take care of the&amp;nbsp;rest.&lt;/p&gt;
&lt;p&gt;And laying out your page?  That whole sidebar thing?  Check out how easy that&amp;nbsp;is:&lt;/p&gt;
&lt;table class="highlighttable"&gt;&lt;tr&gt;&lt;td class="linenos"&gt;&lt;div class="linenodiv"&gt;&lt;pre&gt;&lt;span class="normal"&gt; 1&lt;/span&gt;
&lt;span class="normal"&gt; 2&lt;/span&gt;
&lt;span class="normal"&gt; 3&lt;/span&gt;
&lt;span class="normal"&gt; 4&lt;/span&gt;
&lt;span class="normal"&gt; 5&lt;/span&gt;
&lt;span class="normal"&gt; 6&lt;/span&gt;
&lt;span class="normal"&gt; 7&lt;/span&gt;
&lt;span class="normal"&gt; 8&lt;/span&gt;
&lt;span class="normal"&gt; 9&lt;/span&gt;
&lt;span class="normal"&gt;10&lt;/span&gt;
&lt;span class="normal"&gt;11&lt;/span&gt;
&lt;span class="normal"&gt;12&lt;/span&gt;
&lt;span class="normal"&gt;13&lt;/span&gt;
&lt;span class="normal"&gt;14&lt;/span&gt;
&lt;span class="normal"&gt;15&lt;/span&gt;
&lt;span class="normal"&gt;16&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/td&gt;&lt;td class="code"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="nt"&gt;body&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;display&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;grid&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;grid-template&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;header         header          header&amp;quot;&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;left-sidebar   main-content    right-sidebar&amp;quot;&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;footer         footer          footer&amp;quot;&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="n"&gt;fr&lt;/span&gt;&lt;span class="w"&gt;           &lt;/span&gt;&lt;span class="mi"&gt;6&lt;/span&gt;&lt;span class="n"&gt;fr&lt;/span&gt;&lt;span class="w"&gt;             &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="n"&gt;fr&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="nt"&gt;body&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;header&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;grid-area&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;header&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="p"&gt;#&lt;/span&gt;&lt;span class="nn"&gt;left-sidebar&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;grid-area&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;left&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;sidebar&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="c"&gt;/* ... etc ... */&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;p&gt;Done.  Easy.  It doesn&amp;#8217;t matter what order the parts appear in the markup,&amp;nbsp;either.&lt;/p&gt;
&lt;h3 id="on-the-other-hand"&gt;&lt;a class="toclink" href="#on-the-other-hand"&gt;On the other&amp;nbsp;hand&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;The web is still a &lt;em&gt;little bit&lt;/em&gt; of a disaster.  A lot of folks don&amp;#8217;t even know that flexbox and grid are supported &lt;a href="https://www.caniuse.com/#feat=css-grid"&gt;almost universally&lt;/a&gt; now; but given how long it took to get from early spec work to broad implementation, I can&amp;#8217;t really blame them.  I saw a brand new little site just yesterday that consisted mostly of a huge list of &amp;#8220;thumbnails&amp;#8221; of various widths, and it used floats!  Not even &lt;code&gt;inline-block&lt;/code&gt;!  I don&amp;#8217;t know how we managed to teach everyone about all the hacks required to make that work, but somehow haven&amp;#8217;t gotten the word out about&amp;nbsp;flexbox.&lt;/p&gt;
&lt;p&gt;But far worse than that: I still regularly encounter sites that do their entire page layout with &lt;em&gt;JavaScript&lt;/em&gt;.  If you use &lt;a href="https://addons.mozilla.org/en-US/firefox/addon/umatrix/"&gt;uMatrix&lt;/a&gt;, your first experience is with a pile of text overlapping a pile of other text.  Surely this is a step backwards?  What are you possibly doing that your header and sidebar can only be laid out correctly by executing code?  It&amp;#8217;s not like the page loads with &lt;em&gt;no&lt;/em&gt; &lt;span class="caps"&gt;CSS&lt;/span&gt; — nothing in plain &lt;span class="caps"&gt;HTML&lt;/span&gt; will overlap by default!  You have to tell it to do&amp;nbsp;that!&lt;/p&gt;
&lt;p&gt;And then there&amp;#8217;s the mobile web, which despite everyone&amp;#8217;s good intentions, has kind of turned out to be a failure.  The idea was that you could use &lt;span class="caps"&gt;CSS&lt;/span&gt; media queries to fit your normal site on a phone screen, but instead, most major sites have entirely separate mobile versions.  Which means that either the mobile site is missing a bunch of important features and I&amp;#8217;ll have to awkwardly navigate that on my phone anyway, or the desktop site is full of crap that nobody actually&amp;nbsp;needs.&lt;/p&gt;
&lt;p&gt;(Meanwhile, Google&amp;#8217;s own Android versions of Docs/Sheets/etc. have, like, 5% of the features of the Web versions?  Not sure what to make of&amp;nbsp;that.)&lt;/p&gt;
&lt;p&gt;Hmm.  Strongly considering writing something that goes more into detail about improvements to &lt;span class="caps"&gt;CSS&lt;/span&gt; since the Firefox 3 era, similar to &lt;a href="https://eev.ee/blog/2017/10/07/javascript-got-better-while-i-wasnt-looking/"&gt;the one I wrote for JavaScript&lt;/a&gt;.  But this post is long&amp;nbsp;enough.&lt;/p&gt;
&lt;h2 id="some-futures-that-never-were"&gt;&lt;a class="toclink" href="#some-futures-that-never-were"&gt;Some futures that never&amp;nbsp;were&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;I don&amp;#8217;t know what&amp;#8217;s coming next in &lt;span class="caps"&gt;CSS&lt;/span&gt;, especially now that flexbox and grid have solved all our problems.  I&amp;#8217;m vaguely aware of some work being done on more extensive math support, and possibly some functions for altering colors like in Sass.  There&amp;#8217;s a &lt;a href="https://developer.mozilla.org/en-US/docs/Web/API/CSS_Painting_API"&gt;painting &lt;span class="caps"&gt;API&lt;/span&gt;&lt;/a&gt; that lets you generate backgrounds on the fly with JavaScript using the canvas &lt;span class="caps"&gt;API&lt;/span&gt;, which is&amp;#8230;  quite something.  Apparently it&amp;#8217;s now in spec that you can use &lt;a href="https://developer.mozilla.org/en-US/docs/Web/CSS/attr"&gt;&lt;code&gt;attr()&lt;/code&gt;&lt;/a&gt; (which evaluates to the value of an &lt;span class="caps"&gt;HTML&lt;/span&gt; attribute) as the value for any property, which seems cool and might even let you implement &lt;span class="caps"&gt;HTML&lt;/span&gt; tables entirely in &lt;span class="caps"&gt;CSS&lt;/span&gt;, but you could do the same thing with variables.  I mean, um, custom properties.  I&amp;#8217;m more excited about &lt;a href="https://developer.mozilla.org/en-US/docs/Web/CSS/:is"&gt;&lt;code&gt;:is()&lt;/code&gt;&lt;/a&gt;, which matches any of a list of selectors, and &lt;a href="https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_Grid_Layout/Subgrid"&gt;subgrid&lt;/a&gt;, which lets you add some nesting to a grid but keep grandchildren still aligned to&amp;nbsp;it.&lt;/p&gt;
&lt;p&gt;Much easier is to list some things that &lt;em&gt;were&lt;/em&gt; the future, but fizzled&amp;nbsp;out.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href="https://developer.mozilla.org/en-US/docs/Web/CSS/display"&gt;&lt;code&gt;display: run-in&lt;/code&gt;&lt;/a&gt; has been part of &lt;span class="caps"&gt;CSS&lt;/span&gt; since version 2 (way back in &amp;#8216;98), but it&amp;#8217;s basically unsupported.  The idea is that a &amp;#8220;run-in&amp;#8221; box is inserted, inline, into the next block, so&amp;nbsp;this:&lt;/p&gt;
&lt;table class="highlighttable"&gt;&lt;tr&gt;&lt;td class="linenos"&gt;&lt;div class="linenodiv"&gt;&lt;pre&gt;&lt;span class="normal"&gt;1&lt;/span&gt;
&lt;span class="normal"&gt;2&lt;/span&gt;
&lt;span class="normal"&gt;3&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/td&gt;&lt;td class="code"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;h2&lt;/span&gt; &lt;span class="na"&gt;style&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;display: run-in;&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;Title&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;h2&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;p&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;Paragraph&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;p&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;p&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;Paragraph&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;p&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;p&gt;displays like&amp;nbsp;this:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Title&lt;/strong&gt;&amp;nbsp;Paragraph&lt;/p&gt;
&lt;p&gt;Paragraph&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;And, ah, hm, I&amp;#8217;m starting to see why it&amp;#8217;s unsupported.  It &lt;em&gt;used&lt;/em&gt; to exist in WebKit, but was apparently so unworkable as to be removed six years&amp;nbsp;ago.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;span class="dquo"&gt;&amp;#8220;&lt;/span&gt;Alternate stylesheets&amp;#8221; were popular in the early 00s, at least on a few of my friends&amp;#8217; websites.  The idea was that you could list &lt;em&gt;more than one&lt;/em&gt; stylesheet for your site (presumably for different themes), and the browser would give the user a list of them.  Alas, that list was always squirrelled away in a menu with no obvious indication of when it was actually populated, so in the end, everyone who wanted multiple themes just implemented an in-page theme switcher&amp;nbsp;themselves.&lt;/p&gt;
&lt;p&gt;This feature is &lt;a href="https://developer.mozilla.org/en-US/docs/Web/CSS/Alternative_style_sheets"&gt;still supported&lt;/a&gt;, but apparently Chrome never bothered implementing it, so it&amp;#8217;s effectively&amp;nbsp;dead.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;More generally, the original &lt;span class="caps"&gt;CSS&lt;/span&gt; spec clearly expects users to be able to write their own &lt;span class="caps"&gt;CSS&lt;/span&gt; for a website — right in paragraph 2 it&amp;nbsp;says&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&amp;#8230;the reader may have a personal style sheet to adjust for human or technological&amp;nbsp;handicaps.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Hey, that sounds cool.  But it never materialized as a browser feature.  Firefox has &lt;a href="http://kb.mozillazine.org/UserContent.css"&gt;&lt;code&gt;userContent.css&lt;/code&gt;&lt;/a&gt; and some &lt;span class="caps"&gt;URL&lt;/span&gt; selectors for writing per-site rules, but that&amp;#8217;s relatively&amp;nbsp;obscure.&lt;/p&gt;
&lt;p&gt;Still, there&amp;#8217;s clearly demand for the concept, as evidenced by the popularity of the Stylish extension — which does just this.  (Too bad it was &lt;a href="https://robertheaton.com/2018/07/02/stylish-browser-extension-steals-your-internet-history/"&gt;bought by some chucklefucks who started using it to suck up browser data to sell to advertisers&lt;/a&gt;.  Use &lt;a href="https://addons.mozilla.org/en-US/firefox/addon/styl-us/"&gt;Stylus&lt;/a&gt;&amp;nbsp;instead.)&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;A common problem (well, for me) is that of styling the &lt;em&gt;label&lt;/em&gt; for a checkbox, depending on its state.  Styling the checkbox itself is easy enough with the &lt;code&gt;:checked&lt;/code&gt; pseudo-selector.  But if you arrange a checkbox and its label in the obvious&amp;nbsp;way:&lt;/p&gt;
&lt;table class="highlighttable"&gt;&lt;tr&gt;&lt;td class="linenos"&gt;&lt;div class="linenodiv"&gt;&lt;pre&gt;&lt;span class="normal"&gt;1&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/td&gt;&lt;td class="code"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;label&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;input&lt;/span&gt; &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;checkbox&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; Description of what this does&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;label&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;p&gt;&amp;#8230;then &lt;span class="caps"&gt;CSS&lt;/span&gt; has no way to target either the &lt;code&gt;&amp;lt;label&amp;gt;&lt;/code&gt; element or the text node.  jQuery&amp;#8217;s (originally custom) selector engine offered a custom &lt;code&gt;:has()&lt;/code&gt; pseudo-class, which could be used to express&amp;nbsp;this:&lt;/p&gt;
&lt;table class="highlighttable"&gt;&lt;tr&gt;&lt;td class="linenos"&gt;&lt;div class="linenodiv"&gt;&lt;pre&gt;&lt;span class="normal"&gt;1&lt;/span&gt;
&lt;span class="normal"&gt;2&lt;/span&gt;
&lt;span class="normal"&gt;3&lt;/span&gt;
&lt;span class="normal"&gt;4&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/td&gt;&lt;td class="code"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="c"&gt;/* checkbox label turns bold when checked */&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="nt"&gt;label&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="nd"&gt;has&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nt"&gt;input&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="nd"&gt;checked&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;font-weight&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;bold&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;p&gt;Early &lt;span class="caps"&gt;CSS&lt;/span&gt; 3 selector discussions seemingly wanted to avoid this, I guess for performance reasons?  The somewhat novel alternative was to write out the entire selector, but be able to alter which part of it the rules affected with a &amp;#8220;subject&amp;#8221; indicator.  At first this was a&amp;nbsp;pseudo-class:&lt;/p&gt;
&lt;table class="highlighttable"&gt;&lt;tr&gt;&lt;td class="linenos"&gt;&lt;div class="linenodiv"&gt;&lt;pre&gt;&lt;span class="normal"&gt;1&lt;/span&gt;
&lt;span class="normal"&gt;2&lt;/span&gt;
&lt;span class="normal"&gt;3&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/td&gt;&lt;td class="code"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="nt"&gt;label&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="nd"&gt;subject&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;input&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="nd"&gt;checked&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;font-weight&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;bold&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;p&gt;Then later, they introduced a &lt;code&gt;!&lt;/code&gt; prefix&amp;nbsp;instead:&lt;/p&gt;
&lt;table class="highlighttable"&gt;&lt;tr&gt;&lt;td class="linenos"&gt;&lt;div class="linenodiv"&gt;&lt;pre&gt;&lt;span class="normal"&gt;1&lt;/span&gt;
&lt;span class="normal"&gt;2&lt;/span&gt;
&lt;span class="normal"&gt;3&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/td&gt;&lt;td class="code"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nt"&gt;label&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;input&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="nd"&gt;checked&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;font-weight&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;bold&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;p&gt;Thankfully, this was decided to be a bad idea, so the current specced way to do this is&amp;#8230;  &lt;a href="https://developer.mozilla.org/en-US/docs/Web/CSS/:has"&gt;&lt;code&gt;:has()&lt;/code&gt;&lt;/a&gt;!  Unfortunately, it&amp;#8217;s only allowed when querying from JavaScript, not in a live stylesheet, and nothing implements it anyway.  20 years and I&amp;#8217;m still waiting for a way to style checkbox&amp;nbsp;labels.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;&amp;lt;style scoped&amp;gt;&lt;/code&gt; was an attribute that would&amp;#8217;ve made a &lt;code&gt;&amp;lt;style&amp;gt;&lt;/code&gt; element&amp;#8217;s &lt;span class="caps"&gt;CSS&lt;/span&gt; rules only apply to other elements within its immediate parent, meaning you could drop in arbitrary (possibly user-written) &lt;span class="caps"&gt;CSS&lt;/span&gt; without any risk of affecting the rest of the page.  Alas, this was quietly dropped some time ago, with shadow &lt;span class="caps"&gt;DOM&lt;/span&gt; suggested as a wildly inappropriate&amp;nbsp;replacement.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;I seem to recall that when I first heard about &lt;a href="https://developer.mozilla.org/en-US/docs/Web/Web_Components"&gt;Web components&lt;/a&gt;, they were templates you could use to reduce duplication in pure &lt;span class="caps"&gt;HTML&lt;/span&gt;?  But I can&amp;#8217;t find any trace of that concept now, and the current implementations require JavaScript to define them, so there&amp;#8217;s nothing declarative linking a new tag to its implementation.  Which makes them completely unusable for anything that doesn&amp;#8217;t have a compelling reason to rely on &lt;span class="caps"&gt;JS&lt;/span&gt;.&amp;nbsp;Alas.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;&amp;lt;blink&amp;gt;&lt;/code&gt; and &lt;code&gt;&amp;lt;marquee&amp;gt;&lt;/code&gt;.  &lt;span class="caps"&gt;RIP&lt;/span&gt;.  Though both can be easily replicated with &lt;span class="caps"&gt;CSS&lt;/span&gt;&amp;nbsp;animations.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="thats-it"&gt;&lt;a class="toclink" href="#thats-it"&gt;That's&amp;nbsp;it&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;You&amp;#8217;re still here?  It&amp;#8217;s over.  Go&amp;nbsp;home.&lt;/p&gt;
&lt;p&gt;And maybe push back against Blink monoculture and use &lt;a href="https://www.mozilla.org/en-US/firefox/"&gt;Firefox&lt;/a&gt;, including &lt;a href="https://www.mozilla.org/en-US/firefox/mobile/"&gt;on your phone&lt;/a&gt;, unless for some reason you use an iPhone, which forbids other browser engines, which is far worse than anything Microsoft ever did, but we just kinda accept it for some&amp;nbsp;reason.&lt;/p&gt;</content><category term="blog"></category><category term="tech"></category></entry><entry><title>Eevee gained 3169 experience points</title><link href="https://eev.ee/blog/2020/01/14/eevee-gained-3169-experience-points/" rel="alternate"></link><published>2020-01-14T09:05:00-08:00</published><updated>2020-01-14T09:05:00-08:00</updated><author><name>Eevee</name></author><id>tag:eev.ee,2020-01-14:/blog/2020/01/14/eevee-gained-3169-experience-points/</id><summary type="html">&lt;p&gt;Eevee grew to level 33!&lt;/p&gt;
</summary><content type="html">&lt;p&gt;Eevee grew to level&amp;nbsp;33!&lt;/p&gt;


&lt;p&gt;I had kind of a rough year.  Between medication issues, a lot of interpersonal tangles, and discovering ancient trauma, it feels like my head is full of static a lot of the time, and I don&amp;#8217;t know how to create when I&amp;#8217;m in that state.  I might be able to &lt;em&gt;function&lt;/em&gt;, even do rote programming work, but I just can&amp;#8217;t &lt;em&gt;synthesize&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;And that sucks.  I miss it.  I miss writing!  I barely wrote anything here all year.  I&amp;#8217;ve had a half-finished post open for months and just haven&amp;#8217;t been able to wrap it up and get it&amp;nbsp;out.&lt;/p&gt;
&lt;p&gt;I&amp;#8217;m working on it.  It&amp;#8217;s just&amp;nbsp;hard.&lt;/p&gt;
&lt;hr /&gt;
&lt;p&gt;Ash and I made &lt;a href="https://eevee.itch.io/cherry-kisses"&gt;Cherry Kisses&lt;/a&gt; (&lt;strong&gt;nsfw&lt;/strong&gt;), probably the best puzzle game I&amp;#8217;ve designed and the most polished game we&amp;#8217;ve released, so that was nice.  I also made a &lt;a href="https://eev.ee/release/2019/04/20/particle-wipe-generator/"&gt;particle wipe generator&lt;/a&gt; out of the screen wipe effect I used in the&amp;nbsp;game.&lt;/p&gt;
&lt;p&gt;I started on baz, a game creator meant to kinda blend the styles of MegaZeux and PuzzleScript and bitsy, but it&amp;#8217;s yet to see the light of&amp;nbsp;day.&lt;/p&gt;
&lt;p&gt;I worked a lot on fox flux — adding water physics, redesigning the player sprite, inventing some new mechanics, adding a menu, refactoring to use an &lt;span class="caps"&gt;ECS&lt;/span&gt;-like approach, massively cleaning up my collision code, and whatnot.  I also got stuck in a quagmire of trying to make push physics work how I want, but never actually got it working despite pouring weeks and weeks into it, and now the whole codebase is in a broken shambles.  Kind of a mixed bag&amp;nbsp;there.&lt;/p&gt;
&lt;p&gt;I finally started on &lt;span class="caps"&gt;GLEAM&lt;/span&gt;, an editor for the &lt;span class="caps"&gt;VN&lt;/span&gt; engine I&amp;#8217;ve used for Floraverse for many years now.  It&amp;#8217;s not &lt;em&gt;quite&lt;/em&gt; ready for public use, but it&amp;#8217;s far enough along that I can make VNs with it and only a little manual adjusting, which is&amp;nbsp;cool.&lt;/p&gt;
&lt;p&gt;&lt;a href="https://eev.ee/blog/2019/10/26/goodbye-twigs/"&gt;Twigs&amp;nbsp;died.&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;After half a year of pulling teeth, we managed to get Ash&amp;#8217;s divorce from Marl&amp;nbsp;finalized.&lt;/p&gt;
&lt;p&gt;Ash and I&amp;nbsp;married.&lt;/p&gt;
&lt;p&gt;I did the &lt;a href="https://eev.ee/release/2019/12/01/advent-calendar-2019/"&gt;advent calendar&lt;/a&gt;, which included a dozen or so smaller projects.  That was pretty fun, if a bit&amp;nbsp;ambitious.&lt;/p&gt;
&lt;p&gt;I drew more than the previous year, I think, and probably got better at it.  I even drew some character references, at long&amp;nbsp;last.&lt;/p&gt;
&lt;hr /&gt;
&lt;p&gt;I don&amp;#8217;t know what I&amp;#8217;ll do this year!  I&amp;#8217;m tired of listing a bunch of ambitions and then not being able to do them.  But I&amp;#8217;ll keep&amp;nbsp;trying.&lt;/p&gt;
&lt;!-- stick this down here to keep it out of the preview --&gt;
&lt;p&gt;&lt;audio src="/media/2012-01/levelup.ogv" controls autoplay&gt;&lt;/p&gt;</content><category term="blog"></category><category term="birthday"></category><category term="personal"></category></entry><entry><title>Goodbye, Twigs</title><link href="https://eev.ee/blog/2019/10/26/goodbye-twigs/" rel="alternate"></link><published>2019-10-26T20:12:00-07:00</published><updated>2019-10-26T20:12:00-07:00</updated><author><name>Eevee</name></author><id>tag:eev.ee,2019-10-26:/blog/2019/10/26/goodbye-twigs/</id><summary type="html">&lt;p&gt;&lt;div class="prose-full-illustration"&gt;&lt;img alt="Twigs lounging in a cat tree, while a bright sunbeam illuminates him from behind" src="/media/2019-10-goodbye-twigs/twigs-beautiful-sunbeam.jpg"/&gt;&lt;/div&gt;&lt;/p&gt;
&lt;p&gt;I did not expect my return to writing to be like this.&lt;/p&gt;
&lt;p&gt;Twigs, our nine-year-old sphynx cat, has died.&lt;/p&gt;
&lt;p&gt;He is survived by Pearl, his lovely niece; Anise, his best friend and sparring partner; Cheeseball, his wrestling protégé; and Napoleon, his oldest and dearest friend.&lt;/p&gt;
</summary><content type="html">&lt;p&gt;&lt;div class="prose-full-illustration"&gt;&lt;img src="/media/2019-10-goodbye-twigs/twigs-beautiful-sunbeam.jpg" alt="Twigs lounging in a cat tree, while a bright sunbeam illuminates him from behind"&gt;&lt;/div&gt;&lt;/p&gt;
&lt;p&gt;I did not expect my return to writing to be like&amp;nbsp;this.&lt;/p&gt;
&lt;p&gt;Twigs, our nine-year-old sphynx cat, has&amp;nbsp;died.&lt;/p&gt;
&lt;p&gt;He is survived by Pearl, his lovely niece; Anise, his best friend and sparring partner; Cheeseball, his wrestling protégé; and Napoleon, his oldest and dearest&amp;nbsp;friend.&lt;/p&gt;


&lt;p&gt;&lt;div class="gallery"&gt;
&lt;a href="/media/2019-10-goodbye-twigs/twigs-beautiful-face.jpg" class="photo" title="This is my favorite photo of him. "&gt;&lt;img src="/media/2019-10-goodbye-twigs/twigs-beautiful-face_m.jpg" alt="Very close photo of Twigs, with half his face nearly filling the frame"&gt;&lt;/a&gt;
&lt;a href="/media/2019-10-goodbye-twigs/twigs-beautiful-eye.jpg" class="photo" title="I love his eyes so much. "&gt;&lt;img src="/media/2019-10-goodbye-twigs/twigs-beautiful-eye_m.jpg" alt="Very close photo of Twigs from the side, showing good detail of his eye"&gt;&lt;/a&gt;
&lt;a href="/media/2019-10-goodbye-twigs/twigs-beautiful-skyward.jpg" class="photo" title="Majesty. "&gt;&lt;img src="/media/2019-10-goodbye-twigs/twigs-beautiful-skyward_m.jpg" alt="Twigs looking, majestically, up towards the sky"&gt;&lt;/a&gt;
&lt;a href="/media/2019-10-goodbye-twigs/twigs-lounging-from-below.jpg" class="photo" title="Beautiful. "&gt;&lt;img src="/media/2019-10-goodbye-twigs/twigs-lounging-from-below_m.jpg" alt="Twigs lounging over the edge of a cat tree platform, shot from below"&gt;&lt;/a&gt;
&lt;/div&gt;&lt;/p&gt;
&lt;p&gt;Twigs was Ash&amp;#8217;s¹ cat, more than I have ever known anyone to be anyone&amp;#8217;s cat.  He loved them &lt;em&gt;so&lt;/em&gt; much.  No matter where in the house they went to sit or lie down, Twigs was practically guaranteed to appear a short time later to insert himself into their&amp;nbsp;lap.&lt;/p&gt;
&lt;p&gt;&lt;small&gt;¹ For those who&amp;#8217;ve been following along for some time, &lt;a href="https://twitter.com/glitchedpuppet"&gt;Ash&lt;/a&gt; used to go by Mel.&lt;/small&gt;&lt;/p&gt;
&lt;p&gt;If there was no room for him, or Ash rebuffed him for whatever reason, or if he was just in the mood, his backup plan was to sit somewhere else and keep an eye on them.  Sometimes I&amp;#8217;d be talking to Ash and catch sight of Twigs behind them, staring at them.  Just &lt;em&gt;watching&lt;/em&gt;.  I&amp;#8217;d tell Ash, and they&amp;#8217;d turn around and giggle at him, and he&amp;#8217;d keep on staring.  Sometimes they played hide-and-seek with him, ducking out of sight and then peeking back out at him; he might still be staring, or he might have trotted over to see where they went.  Or they could call out to him, just say his name, and he&amp;#8217;d acknowledge them with a little meow and come over.  They could summon him silently, too, with nothing more than eye contact and a particular&amp;nbsp;nod.&lt;/p&gt;
&lt;p&gt;Sometimes we&amp;#8217;d be sitting apart and Twigs would sit on me instead, laying chest-to-chest against me.  He&amp;#8217;d play this ridiculous game where he&amp;#8217;d nuzzle my chin a few times, then &lt;em&gt;look at Ash&lt;/em&gt; for a moment before doing it again.  As if to say, hey, look what you&amp;#8217;re missing out on.  Or maybe just to say he hadn&amp;#8217;t forgotten about&amp;nbsp;them.&lt;/p&gt;
&lt;p&gt;Twigs liked to sit at the top of the cat tree in our dining room, right in the path of a huge sunbeam for much of the day, where he could watch Ash at their desk and also see most of the house.  We got a huge beanbag over the summer and put it behind Ash&amp;#8217;s desk, and Twigs spent a lot of time there as well.  He did his own thing at times, certainly, but it was rare for a day to go by without Twigs trying to be close to&amp;nbsp;Ash.&lt;/p&gt;
&lt;p&gt;If Ash was inaccessible — in someone else&amp;#8217;s bedroom with the door closed, or in the backyard, or even in the bathroom for too long — Twigs would sit at the objectionable door and yell for them.  I can&amp;#8217;t think of many other cat meow I&amp;#8217;d describe as a &lt;em&gt;yell&lt;/em&gt;, but that&amp;#8217;s definitively what Twigs did.  &lt;em&gt;&lt;span class="caps"&gt;MYAOOOW&lt;/span&gt;?&lt;/em&gt;  &lt;em&gt;&lt;span class="caps"&gt;MYEHHHH&lt;/span&gt;!&lt;/em&gt;  When Ash was out of town, I&amp;#8217;d often hear him trotting up and down the upstairs hallway, yelling for them — until he gave up looking for the moment and came to snuggle with me, just as intensely, like I were the one he&amp;#8217;d been looking for all&amp;nbsp;along.&lt;/p&gt;
&lt;p&gt;His favorite thing in the world was bedtime, when Ash would finally not be distracted by anything else, and he could lay with them all night.  All the cats sleep with us to varying degrees, but Twigs was usually the first to show up.  His arrival was so distinct: the quiet footsteps, the weight on the bed, and then the purr would start up before we could even see him.  He&amp;#8217;d spend all night with us most nights, laying on Ash&amp;#8217;s chest in the classic Sphinx pose or curled up behind their knees under the&amp;nbsp;blanket.&lt;/p&gt;
&lt;p&gt;I loved how frequently he showed up &lt;em&gt;already&lt;/em&gt; purring, apparently anticipating how good of a time he was about to have.  It came across as this comical overconfidence, like he took for granted that of &lt;em&gt;course&lt;/em&gt; he would be involved in whatever Ash was doing.  But his purr, as common and subdued as it was, was such a deep and full and genuine rumble.  He made me feel like I&amp;#8217;d &lt;em&gt;earned&lt;/em&gt; it, like I must&amp;#8217;ve done something truly admirable to earn this level of praise.  I always called it regal.  The purr of a&amp;nbsp;king.&lt;/p&gt;
&lt;p&gt;&lt;div class="gallery"&gt;
&lt;a href="/media/2019-10-goodbye-twigs/twigs-ash-sleeping-together.jpg" class="photo" title="They say pets look like their owners... "&gt;&lt;img src="/media/2019-10-goodbye-twigs/twigs-ash-sleeping-together_m.jpg" alt="Twigs and Ash asleep in bed next to each other, both sticking out from the blankets in similar poses"&gt;&lt;/a&gt;
&lt;a href="/media/2019-10-goodbye-twigs/twigs-ash-lap.jpg" class="photo" title="I see claw trimmers in their hand, so this mood won&amp;#x27;t last long. "&gt;&lt;img src="/media/2019-10-goodbye-twigs/twigs-ash-lap_m.jpg" alt="Twigs sitting in Ash&amp;#x27;s lap, leaning back against them and almost sitting like a person"&gt;&lt;/a&gt;
&lt;a href="/media/2019-10-goodbye-twigs/twigs-ash-fireplace.jpg" class="photo" title="Nice and toasty. "&gt;&lt;img src="/media/2019-10-goodbye-twigs/twigs-ash-fireplace_m.jpg" alt="Twigs and Ash sharing the same chair and warming themselves in front of a fireplace"&gt;&lt;/a&gt;
&lt;a href="/media/2019-10-goodbye-twigs/twigs-goofy-yawn.jpg" class="photo" title="Not only is he comfortable in this pose, but it makes him sleepy. "&gt;&lt;img src="/media/2019-10-goodbye-twigs/twigs-goofy-yawn_m.jpg" alt="Twigs yawning while held wrapped in a robe, with all four limbs awkwardly sticking out"&gt;&lt;/a&gt;
&lt;a href="/media/2019-10-goodbye-twigs/twigs-dangle.jpg" class="photo" title="I love dangle Twigs. "&gt;&lt;img src="/media/2019-10-goodbye-twigs/twigs-dangle_m.jpg" alt="Twigs held vertically around his chest by Ash"&gt;&lt;/a&gt;
&lt;a href="/media/2019-10-goodbye-twigs/twigs-ash-neck-scratch.jpg" class="photo" title="He&amp;#x27;s getting his neck scratched here, which was the only time he made this goofy face. "&gt;&lt;img src="/media/2019-10-goodbye-twigs/twigs-ash-neck-scratch_m.jpg" alt="Twigs making a delighted (albeit weird) face as he&amp;#x27;s held and has his neck scratched by Ash"&gt;&lt;/a&gt;
&lt;a href="/media/2019-10-goodbye-twigs/twigs-ash-lounge-phone.jpg" class="photo" title="Twigs and Ash, always together. "&gt;&lt;img src="/media/2019-10-goodbye-twigs/twigs-ash-lounge-phone_m.jpg" alt="Ash lying on their side checking their phone, while Twigs lays over their free arm"&gt;&lt;/a&gt;
&lt;a href="/media/2019-10-goodbye-twigs/twigs-anise-arm-over-shoulder.jpg" class="photo" title="Twigs made fast friends with Kitten Anise. "&gt;&lt;img src="/media/2019-10-goodbye-twigs/twigs-anise-arm-over-shoulder_m.jpg" alt="Ash holding Twigs and Anise; Twigs has his arm over Anise&amp;#x27;s shoulder"&gt;&lt;/a&gt;
&lt;/div&gt;&lt;/p&gt;
&lt;p&gt;In the early morning hours of October 13, early enough that it was still the previous night, Twigs came downstairs and yelled.  That wasn&amp;#8217;t unusual; he&amp;#8217;d yell for Ash&amp;#8217;s attention all the time.  But then he lay on his stomach, angled straight up like the actual Sphinx, a pose he exclusively reserved for comfy places like laps and cat&amp;nbsp;beds.&lt;/p&gt;
&lt;p&gt;Ash and I went over to check him out, but we couldn&amp;#8217;t find any tender spots, injuries, or other obvious problems.  My best guess was a stomachache, which wasn&amp;#8217;t unheard of for Twigs; perhaps laying on his stomach helped settle it?  The room was a little chilly and he wasn&amp;#8217;t wearing a sweater, so Ash wrapped him in a blanket and set him on the beanbag he liked, in the path of a heat&amp;nbsp;lamp.&lt;/p&gt;
&lt;p&gt;We went to bed only an hour or so later, and Ash carried Twigs with them.  Without the heat lamp on him, he was noticeably cold to the touch now, and starting to stumble.  I didn&amp;#8217;t think of it until later, but as cold as he was, he never shivered&amp;nbsp;once.&lt;/p&gt;
&lt;p&gt;We rushed him to a 24/7 emergency&amp;nbsp;vet.&lt;/p&gt;
&lt;p&gt;His temperature was 92 when we arrived.  Normal body temperature for cats is around&amp;nbsp;100.&lt;/p&gt;
&lt;p&gt;They set about warming him up, rushed through some authorizations, drew some blood, told us results would come in about thirty&amp;nbsp;minutes.&lt;/p&gt;
&lt;p&gt;Twigs didn&amp;#8217;t make it that long.  At 4:26 in the morning, cold and confused, somewhere in a sterile room apart from everyone he&amp;#8217;d ever known and loved, his heart&amp;nbsp;stopped.&lt;/p&gt;
&lt;p&gt;Only three or four hours had passed since he first showed any signs of distress whatsoever, and Twigs was&amp;nbsp;gone.&lt;/p&gt;
&lt;p&gt;&lt;div class="gallery"&gt;
&lt;a href="/media/2019-10-goodbye-twigs/twigs-peer-over-shoulder.jpg" class="photo" title="Oh, hey, I didn&amp;#x27;t see you there. "&gt;&lt;img src="/media/2019-10-goodbye-twigs/twigs-peer-over-shoulder_m.jpg" alt="Twigs sat looking over his shoulder at the camera, which is behind him"&gt;&lt;/a&gt;
&lt;a href="/media/2019-10-goodbye-twigs/twigs-peer-direct.jpg" class="photo" title="Thanks for coming to my TED Talk. "&gt;&lt;img src="/media/2019-10-goodbye-twigs/twigs-peer-direct_m.jpg" alt="Twigs peering directly into the camera"&gt;&lt;/a&gt;
&lt;a href="/media/2019-10-goodbye-twigs/twigs-cool-face.jpg" class="photo" title="I still have no idea what this one means. "&gt;&lt;img src="/media/2019-10-goodbye-twigs/twigs-cool-face_m.jpg" alt="Twigs, seemingly in mid-squabble with a young Anise, looking over his shoulder with a mysterious expression"&gt;&lt;/a&gt;
&lt;a href="/media/2019-10-goodbye-twigs/twigs-beautiful-sweater.jpg" class="photo" title="You can see what he does to his sweaters. "&gt;&lt;img src="/media/2019-10-goodbye-twigs/twigs-beautiful-sweater_m.jpg" alt="Twigs in a pink and white sweater, perched on the back of a couch and looking at the camera"&gt;&lt;/a&gt;
&lt;/div&gt;&lt;/p&gt;
&lt;p&gt;Twigs was &lt;em&gt;so&lt;/em&gt; expressive!  He had so much personality, and he showed all of it.  Sphynxes seem a little easier to read than furred cats, but&amp;#8230;  well, Pearl is a little reserved, and Anise is downright incomprehensible.  Twigs was an open&amp;nbsp;book.&lt;/p&gt;
&lt;p&gt;Photos don&amp;#8217;t quite do him justice, since cats are easiest to photograph when they&amp;#8217;re relaxing.  All of his body language and facial expressions felt really crisp and distinct, like he wanted you to know what he was thinking, but didn&amp;#8217;t want to ham it up.  How do I even explain this?  How would I explain the faces a human makes,&amp;nbsp;even?&lt;/p&gt;
&lt;p&gt;His &amp;#8220;I love sitting on you&amp;#8221; face, his &amp;#8220;I want to eat that&amp;#8221; face, his &amp;#8220;this is a bit annoying but I&amp;#8217;ll put up with it&amp;#8221; face&amp;#8230;  they were all so clear and distinct, moreso than any of our other cats, moreso than any cat I&amp;#8217;ve met.  He&amp;#8217;d even turn up the corners of his mouth when he was really happy, making a little cat&amp;nbsp;smile.&lt;/p&gt;
&lt;p&gt;His eyes were huge and beautiful, and we got to see them a lot while he played sentinel, perched somewhere with a good field of view.  They were different colors, too!  Only slightly, but in the right light, one was distinctly greener and the other distinctly bluer.  It was obvious from a glance at his eyes whether he was staring into space, watching you, wanting something from you, or wanting to come over to&amp;nbsp;you.&lt;/p&gt;
&lt;p&gt;He was always, &lt;em&gt;always&lt;/em&gt; delighted when someone would pet him.  I don&amp;#8217;t think Twigs &lt;em&gt;ever&lt;/em&gt; acted solitary; he stands out as the most readily and consistently affectionate cat we&amp;#8217;ve had.  He even had a specific expression for when he was in a good mood and wanted someone to pet him, which I called &amp;#8220;bedroom eyes&amp;#8221; — both because he lidded his eyes a bit, and because he mostly did it when laying in bed with us.  If he was especially happy, he&amp;#8217;d come lie on your chest, scoot forwards as far as he possibly could, and give you super nuzzles all over your&amp;nbsp;chin.&lt;/p&gt;
&lt;p&gt;Twigs had a very pettable head, too.  Broad, with his ears more to the sides.  I always said he had a cheese head, because it reminded me of a cheese wedge?  For some reason?  He had a good cheese head, perfect for kissing (&amp;#8220;kitten kisses&amp;#8221;), which he seemed to understand was a sign of affection.  He loved having his head pet so much that he&amp;#8217;d keep tilting his head further and further back, ostensibly to press harder against your hand — but if he was perched on the top level of a cat tree, that made it harder to &lt;em&gt;reach&lt;/em&gt; the top of his head, so you&amp;#8217;d have to do this silly little negotiation with him.  It made his smile all the easier to see,&amp;nbsp;though.&lt;/p&gt;
&lt;p&gt;He had some other quirky little &amp;#8220;tells&amp;#8221; that seemed subtle, but that gave away what he&amp;#8217;d almost certainly do next: hesitating in a particular way before inexplicably dashing away, or looking up and around at the ceiling before doing a big&amp;nbsp;meow.&lt;/p&gt;
&lt;p&gt;His meows!  Twigs had a huge vocabulary, and so much of it was for asking politely for things.  His &amp;#8220;yell&amp;#8221; for when he wanted Ash was big and boisterous, with a little characteristic warble to it, and he opened his mouth &lt;em&gt;comically&lt;/em&gt; wide when he did it.  If he wanted Ash&amp;#8217;s steak scraps (which he &lt;em&gt;loved&lt;/em&gt;), he had a very reserved meow for asking for them.  If he couldn&amp;#8217;t get under a blanket, he had a different reserved meow for asking for help.  He was the only cat who regularly did that funny chirpy meow at bugs on the wall, though we hadn&amp;#8217;t heard that one since we left the Seattle area — Vegas didn&amp;#8217;t have nearly as many&amp;nbsp;bugs.&lt;/p&gt;
&lt;p&gt;When Anise would roughhouse a bit too hard, Twigs had a distinct pained meow for &amp;#8220;this is too much&amp;#8221; that would bring one of us running.  I didn&amp;#8217;t hear it much after we got Cheeseball, who acts as a more eager sparring partner for Anise, until one day I heard a distorted version of it — and I found Twigs and Cheeseball happily wrestling!  Twigs came up with a new meow, ending on a happy note rather than a painful one, just for when he was playing with this new giant kitten&amp;nbsp;friend.&lt;/p&gt;
&lt;p&gt;One of the most frustrating parts of this is that it&amp;#8217;s so hard to capture a cat&amp;#8217;s meows, or a lot of other subtleties.  As vocal as Twigs was, he still only spoke when he had something to say, and that was rarely when he was in front of a camera.  I remember them so clearly now, but how can I convey them in text?  &lt;em&gt;Myehhh&lt;/em&gt; doesn&amp;#8217;t really cut it.  (I&amp;#8217;ve been sorting through old cat videos, but it&amp;#8217;s slow going; I&amp;#8217;ll throw some of them up somewhere in the near&amp;nbsp;future.)&lt;/p&gt;
&lt;p&gt;&lt;div class="gallery"&gt;
&lt;a href="/media/2019-10-goodbye-twigs/twigs-bedroom-eyes.jpg" class="photo" title="This is what I mean by bedroom eyes.  I can guarantee that he&amp;#x27;s purring and also hoping I will pet him. "&gt;&lt;img src="/media/2019-10-goodbye-twigs/twigs-bedroom-eyes_m.jpg" alt="Twigs lying propped up on me in bed, looking at the camera with lidded eyes"&gt;&lt;/a&gt;
&lt;a href="/media/2019-10-goodbye-twigs/twigs-on-chest-pov.jpg" class="photo" title="A simulation of what Twigs&amp;#x27;s company was like. "&gt;&lt;img src="/media/2019-10-goodbye-twigs/twigs-on-chest-pov_m.jpg" alt="Twigs laying on my chest"&gt;&lt;/a&gt;
&lt;a href="/media/2019-10-goodbye-twigs/twigs-smile.jpg" class="photo" title="I love his smile so much. "&gt;&lt;img src="/media/2019-10-goodbye-twigs/twigs-smile_m.jpg" alt="Twigs looking very pleased, with perhaps the hint of a smile, as I pet his head"&gt;&lt;/a&gt;
&lt;a href="/media/2019-10-goodbye-twigs/twigs-beautiful-forehead.jpg" class="photo" title="I love his beautiful cheese head, too. "&gt;&lt;img src="/media/2019-10-goodbye-twigs/twigs-beautiful-forehead_m.jpg" alt="Twigs looking content as he&amp;#x27;s pet, his head tilted down so the camera shows the top of it"&gt;&lt;/a&gt;
&lt;/div&gt;&lt;/p&gt;
&lt;p&gt;I don&amp;#8217;t understand what&amp;nbsp;happened.&lt;/p&gt;
&lt;p&gt;The test results only showed that he was severely anemic — he had far too few red blood cells, so he couldn&amp;#8217;t warm himself or get enough oxygen.  They didn&amp;#8217;t explain how he&amp;#8217;d reached that point in a matter of &lt;em&gt;hours&lt;/em&gt; without showing milder symptoms&amp;nbsp;first.&lt;/p&gt;
&lt;p&gt;The day had been entirely normal.  Twigs had been happy and active earlier in the afternoon.  He wasn&amp;#8217;t in the habit of chewing or eating strange things.  We keep all our cats indoors, and the others are still fine, so he couldn&amp;#8217;t have picked up a communicable illness.  If he&amp;#8217;d ever shown any sign that anything was wrong, I know with &lt;strong&gt;absolute certainty&lt;/strong&gt; that Ash would&amp;#8217;ve noticed, just as I immediately noticed when my cat Styx had lost weight.  But there was&amp;nbsp;nothing.&lt;/p&gt;
&lt;p&gt;What, then, actually happened to him?  I don&amp;#8217;t know.  I&amp;#8217;ll never know.  I briefly thought to ask for an autopsy, but at the time, I couldn&amp;#8217;t bear the thought of what that would&amp;#8230;  &lt;em&gt;mean&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;No explanation, no reason, nothing to blame.  Twigs was his healthy happy self all day, all week, all month, all year.  Right up until he wasn&amp;#8217;t.  And then he&amp;nbsp;died.&lt;/p&gt;
&lt;p&gt;&lt;div class="gallery"&gt;
&lt;a href="/media/2019-10-goodbye-twigs/twigs-groomed-by-anise.jpg" class="photo" title="He loved being groomed by the other cats. "&gt;&lt;img src="/media/2019-10-goodbye-twigs/twigs-groomed-by-anise_m.jpg" alt="Twigs looking serene as Anise licks his ear, both of them together in the same cat bed"&gt;&lt;/a&gt;
&lt;a href="/media/2019-10-goodbye-twigs/twigs-grooming-anise.jpg" class="photo" title="You next. "&gt;&lt;img src="/media/2019-10-goodbye-twigs/twigs-grooming-anise_m.jpg" alt="Twigs licking Anise&amp;#x27;s ear while holding him still with a paw"&gt;&lt;/a&gt;
&lt;a href="/media/2019-10-goodbye-twigs/twigs-ears-groomed-by-napoleon.jpg" class="photo" title="Of course it&amp;#x27;s the ears.  He&amp;#x27;s still glad for it, though. "&gt;&lt;img src="/media/2019-10-goodbye-twigs/twigs-ears-groomed-by-napoleon_m.jpg" alt="Twigs looking serene as Napoleon licks the back of his ear"&gt;&lt;/a&gt;
&lt;a href="/media/2019-10-goodbye-twigs/twigs-anise-cuddling.jpg" class="photo" title="Anise started a lot of fights, but they were still good friends. "&gt;&lt;img src="/media/2019-10-goodbye-twigs/twigs-anise-cuddling_m.jpg" alt="Twigs and Anise laying in a cat bed facing each other, their paws around each other like an embrace"&gt;&lt;/a&gt;
&lt;/div&gt;&lt;/p&gt;
&lt;p&gt;Twigs was so friendly.  Kind, even.  He never hurt anyone; he rarely did anything unexpected or rambunctious.  He rarely even messed with things he shouldn&amp;#8217;t, in sharp contrast to Anise, who tries to push my phone off my desk anytime he wants my attention; the most Twigs would do was gingerly tap something with a paw to see if it would react, then move&amp;nbsp;on.&lt;/p&gt;
&lt;p&gt;(Well, with one exception.  If he found an unguarded glass of water, but the water level was too low for him to reach it, he was smart enough to tip the whole glass over and douse everything on your desk.  We switched to reusable water bottles years&amp;nbsp;ago.)&lt;/p&gt;
&lt;p&gt;I can&amp;#8217;t think of a single time Twigs was mean or angry or even wanted to be alone.  All the cats have times they&amp;#8217;re comfortable and don&amp;#8217;t want to be disturbed, or just aren&amp;#8217;t in the mood, or whatever — except&amp;nbsp;Twigs.&lt;/p&gt;
&lt;p&gt;If Ash scolded him (&amp;#8220;Twigs!&amp;#8221;), he&amp;#8217;d dash off to a cat tree and scrabble at it briefly, taking his frustrations out with a few quick scratches and this funny little shimmy of his hips, then forget all about it.  In extreme cases, he might run upstairs to our empty bedroom, yell once or twice, then come back down.  Or in milder cases, when he couldn&amp;#8217;t get something he wanted, he&amp;#8217;d snort audibly and that was that.  It was so, so charming — if he was upset, all he needed to do was go somewhere to yell about it for a moment, and then he was&amp;nbsp;fine.&lt;/p&gt;
&lt;p&gt;He was so patient, too.  Ash put little costumes on him a few times, which he took in stride — well, for a cat, at least.  He was always happy to be picked up, wrapped in clothing or a blanket, and/or held in all manner of silly positions.  You could check his teeth and he&amp;#8217;d hardly mind at all.  Play with his ears, shake his paw, squish his lip, whatever; he was content just to be interacted with.  (I suspect there was some mutual reinforcement between Ash doing goofy things to Twigs, and Twigs laying in increasingly obnoxious ways on&amp;nbsp;Ash.)&lt;/p&gt;
&lt;p&gt;He didn&amp;#8217;t much like having his claws trimmed, and when Ash would do it, he used to bite the squishy part of their thumb — but not bite &lt;em&gt;down&lt;/em&gt;, only put his teeth around their hand.  Enough to communicate &amp;#8220;I don&amp;#8217;t like this&amp;#8221; without trying to hurt them.  Ash eventually started bribing him with cat treats every few claws, and then he disliked the process a bit&amp;nbsp;less.&lt;/p&gt;
&lt;p&gt;His good nature extended to the other cats, as well.  He befriended every cat we&amp;#8217;ve ever had!  I didn&amp;#8217;t really think about it until after he died, but if I ever saw two or more cats hanging out together, Twigs was almost guaranteed to be one of them.  He was the binding force of our little cat&amp;nbsp;sitcom.&lt;/p&gt;
&lt;p&gt;There was one brief exception, when Ash first adopted Pearl — the first new cat since Twigs that was 100% Ash&amp;#8217;s.  They kept Pearl with them all the time at first, and Twigs got &lt;em&gt;so&lt;/em&gt; jealous.  Very early on he made his feelings very clear: he stood on the other side of the room, stared right at Ash (and Pearl), and made a huge meow at them.  Then after like three days he found out that he and Pearl could both fit in Ash&amp;#8217;s lap and everything was&amp;nbsp;fine.&lt;/p&gt;
&lt;p&gt;He&amp;#8217;d cozy up with Anise or Pearl for warmth, and we&amp;#8217;d often see all three of them nestled together, as though Twigs&amp;#8217;s soothing presence deterred Anise and Pearl from their usual squabbling.  He had an awkward but friendly relationship with Napoleon, the most aloof of the cats by far, who doesn&amp;#8217;t show much affection towards any of the others except Pearl.  I remember Napoleon used to refuse to groom Twigs anywhere but on the backs of his ears (the only place he had fur!), but after some years together, we started to see Napoleon grooming Twigs&amp;#8217;s face and neck as well.  For Napoleon, that was a really close&amp;nbsp;friendship.&lt;/p&gt;
&lt;p&gt;Twigs was even friends with Apollo, the German shepherd we used to have, who was &lt;em&gt;much&lt;/em&gt; bigger than this tiny bald cat.  I have a &lt;a href="https://www.youtube.com/watch?v=30PUsNUQTyQ"&gt;video of Twigs and Apollo playing&lt;/a&gt;, where Apollo is gently nudging Twigs around with his nose and Twigs alternates between nuzzling and lightly smacking Apollo.  What a sweetheart.  I don&amp;#8217;t think any other cat interacted with Apollo quite like&amp;nbsp;that.&lt;/p&gt;
&lt;p&gt;He had a somewhat more complicated dynamic with Anise, who&amp;#8217;s a good bit rowdier and more&amp;#8230;  destructive.  Anise liked to start little brawls a lot, which wasn&amp;#8217;t quite Twigs&amp;#8217;s usual style, but he&amp;#8217;d play along until Anise got too rough.  (It probably didn&amp;#8217;t help that Twigs would often respond by grabbing Anise by the sweater, which allowed Anise to wriggle backwards out of it and unleash his full&amp;nbsp;powers.)&lt;/p&gt;
&lt;p&gt;It&amp;#8217;s been funny looking at older photos; when we first got Anise, Twigs was &lt;em&gt;pristine&lt;/em&gt;, with maybe a scar or two on his haunch somewhere.  (And all down the top of his tail, which he liked to nibble with some intensity.)  At the end of his life, Twigs was riddled with little round scars from where Anise had bitten his back, and even a conspicuous dark spot right on top of his head.  Who bites someone&amp;#8217;s &lt;em&gt;head&lt;/em&gt;?&lt;/p&gt;
&lt;p&gt;I don&amp;#8217;t remember his relationship with Styx as clearly, but I have enough photo evidence of it.  The two of them were very close and spent a lot of time snuggled together, whether sleeping or just hanging out.  We even got them matching pink sweaters!  I&amp;#8217;d forgotten that was deliberate.  They played together, too, though much less seriously than Anise and on more &amp;#8220;even&amp;#8221;&amp;nbsp;terms.&lt;/p&gt;
&lt;p&gt;&lt;div class="gallery"&gt;
&lt;a href="/media/2019-10-goodbye-twigs/twigs-royal-attire.jpg" class="photo" title="The only cat sweater worthy of his rank.  He didn&amp;#x27;t think much of the crown, alas. "&gt;&lt;img src="/media/2019-10-goodbye-twigs/twigs-royal-attire_m.jpg" alt="Twigs dressed in a little royal costume, with a red cloak and fancy plush crown"&gt;&lt;/a&gt;
&lt;a href="/media/2019-10-goodbye-twigs/twigs-ascot.jpg" class="photo" title="Rich in friendship, and also money. "&gt;&lt;img src="/media/2019-10-goodbye-twigs/twigs-ascot_m.jpg" alt="Twigs wearing a cat-sized ascot, standing on a table amongst a pool of coins"&gt;&lt;/a&gt;
&lt;a href="/media/2019-10-goodbye-twigs/twigs-ascot-blep.jpg" class="photo" title="Blep. "&gt;&lt;img src="/media/2019-10-goodbye-twigs/twigs-ascot-blep_m.jpg" alt="Similar to the previous photo, but Twigs is sticking his tongue out"&gt;&lt;/a&gt;
&lt;a href="/media/2019-10-goodbye-twigs/twigs-investigating-pearl.jpg" class="photo" title="Just after they met, when Twigs was still very suspicious of Pearl. "&gt;&lt;img src="/media/2019-10-goodbye-twigs/twigs-investigating-pearl_m.jpg" alt="Twigs looking suspiciously at his very young niece Pearl, who is tucked in a blanket and sleeping"&gt;&lt;/a&gt;
&lt;/div&gt;&lt;/p&gt;
&lt;p&gt;Six and a half years ago, my own cat &lt;a href="https://eev.ee/blog/2013/04/30/goodbye-styx/"&gt;Styx died&lt;/a&gt;.  He&amp;#8217;d been &lt;em&gt;my&lt;/em&gt; cat, the way Twigs was Ash&amp;#8217;s cat, sticking to me like glue the whole time I had him.  But then Styx contracted a cruel and incurable illness, one that can strike even indoor cats and prefers to take the young.  He wasted away over the course of a&amp;nbsp;month.&lt;/p&gt;
&lt;p&gt;I&amp;#8217;d like to think that, whatever it was that took Twigs from us, maybe this swift departure saved him from the kind of long and excruciating ordeal that Styx went&amp;nbsp;through.&lt;/p&gt;
&lt;p&gt;I wrote his eulogy the day after he died.  I avoided looking at it for years, but finally went back and read it a few days ago.  It seemed so &lt;em&gt;short&lt;/em&gt;!  Was that really all I had to say about him?  I knew him for over a year, yet I feel like I barely got to know him — I think Cheeseball is already older than Styx was when he died, and Cheeseball&amp;#8217;s personality is still rapidly&amp;nbsp;developing.&lt;/p&gt;
&lt;p&gt;I was more shocked to find my own tweets from soon after Styx&amp;#8217;s death, saying I couldn&amp;#8217;t even look at photos of him.  How long did that last?  I don&amp;#8217;t&amp;nbsp;remember.&lt;/p&gt;
&lt;p&gt;It hurt too much, so I avoided his memory, and now so much of it is a fragmented blur.  Watching him deterioriate was gut-wrenching, and the worst part of his life — but it&amp;#8217;s what I spilled the most ink on, and the part I need the least help remembering.  Why did I write &lt;em&gt;so much&lt;/em&gt; about that month?  None of it was important in the end, yet I liveblogged every gratuitous medical detail.  I guess I didn&amp;#8217;t know what else to do, watching Styx wither away in my arms, while I couldn&amp;#8217;t do anything about&amp;nbsp;it.&lt;/p&gt;
&lt;p&gt;I still cry for him, sometimes.  I get a little sad over something else, and I remember Styx, and I cry.  No matter how many of the details fade, I know I had a little cat named Styx who I loved dearly, and he loved me&amp;nbsp;back.&lt;/p&gt;
&lt;p&gt;This feels like a second chance, though.  I won&amp;#8217;t make the same mistake&amp;nbsp;again.&lt;/p&gt;
&lt;p&gt;It was hard to grieve with Ash all those years ago, back when things were so awkward.  Now we can mourn together, and thinking about Twigs doesn&amp;#8217;t sting the way thinking about Styx used to.  It finally feels okay to remember Styx, too, and I&amp;#8217;ve been rediscovering some old moments as I&amp;#8217;ve sorted through photos in search of&amp;nbsp;Twigs.&lt;/p&gt;
&lt;p&gt;We&amp;#8217;ve been celebrating and filling our space with &lt;em&gt;both&lt;/em&gt; of them — we printed out physical copies of our favorite photo of each and put them in little thematic frames.  Their pawprint casts are together on a shelf behind Ash&amp;#8217;s desk.  Nearby is Twigs&amp;#8217;s urn, and I&amp;#8217;d like to put Styx&amp;#8217;s humble grave marker next to it, once I figure out where I packed it.  Ash is painting portraits of&amp;nbsp;them.&lt;/p&gt;
&lt;p&gt;At my suggestion, we threw Twigs a little goodbye party — I baked a pumpkin cake (in honor of his homemade pumpkin cat food and the one fall he loved a tiny pumpkin), Ash decorated it, and we talked about Twigs and all the things about him that we miss.  I insisted we wear party&amp;nbsp;hats.&lt;/p&gt;
&lt;p&gt;I&amp;#8217;ve been taking notes on his life ever since he died, all so I could write this eulogy for him.  It&amp;#8217;s intimidating and even more difficult than I expected, trying to capture a life that meant so much to us in only a few thousand words.  I hope I&amp;#8217;m doing him justice.  I want everyone to know how good Twigs was, and how much we&amp;#8217;ve&amp;nbsp;lost.&lt;/p&gt;
&lt;p&gt;&lt;div class="gallery"&gt;
&lt;a href="/media/2019-10-goodbye-twigs/twigs-party-cake.jpg" class="photo" title="I&amp;#x27;d never tried to make decorative icing before, but Ash did a fantastic job with what I gave them.  Also it was fucking delicious. "&gt;&lt;img src="/media/2019-10-goodbye-twigs/twigs-party-cake_m.jpg" alt="An iced cake with Twigs drawn in colored icing, twigs and hearts around it, and the text: &amp;#x27;We love and miss you!&amp;#x27;"&gt;&lt;/a&gt;
&lt;a href="/media/2019-10-goodbye-twigs/twigs-party.jpg" class="photo" title="Party setup, complete with our new framed photos.  This stuff was all less than a dollar. "&gt;&lt;img src="/media/2019-10-goodbye-twigs/twigs-party_m.jpg" alt="A table half-covered with a cheap plastic tablecloth and set with paper plates, cups, and party hats; in the middle is a cake and in the back are two photos in wooden frames"&gt;&lt;/a&gt;
&lt;a href="/media/2019-10-goodbye-twigs/twigs-party-streamers.jpg" class="photo" title="Cheeseball really, really wants those party streamers. "&gt;&lt;img src="/media/2019-10-goodbye-twigs/twigs-party-streamers_m.jpg" alt="Cheeseball, our Lykoi, looking up at paper streamers hanging over his head"&gt;&lt;/a&gt;
&lt;a href="/media/2019-10-goodbye-twigs/twigs-party-ash.jpg" class="photo" title="Ash, of course, cut the cake. "&gt;&lt;img src="/media/2019-10-goodbye-twigs/twigs-party-ash_m.jpg" alt="Ash wearing a party hat and removing the first corner of the cake with an icing spreader"&gt;&lt;/a&gt;
&lt;/div&gt;&lt;/p&gt;
&lt;p&gt;Twigs had his sassy side, but it was always sweet and harmless.  Less like typical cat aloofness, more like that charming confidence of showing up to cuddle with his purr already in full swing, completely taking for granted that he was welcome and was about to enjoy himself.  Or the similar energy he put on display when you were on a couch and he wanted to sit on you: he&amp;#8217;d identify the most Twigs-shaped nook on your body and wedge his butt backwards into it, sometimes even hoisting himself with his front legs a bit, like a human settling into a&amp;nbsp;recliner.&lt;/p&gt;
&lt;p&gt;For example: if Twigs tried to approach Ash but Ash pushed him away — e.g., because they were eating or painting or their lap was occupied — then Twigs would often do a complete circle around the table or part of the room, only to approach Ash again from the other direction.  It was so comical!  So gentle and friendly, but cheerfully defiant about being near Ash.  As if he couldn&amp;#8217;t even imagine that &lt;em&gt;he&lt;/em&gt; was disallowed for the moment.  The problem must have been with his &lt;em&gt;approach&lt;/em&gt;.  There&amp;#8217;s just no other rational&amp;nbsp;explanation.&lt;/p&gt;
&lt;p&gt;Since living in Colorado, we&amp;#8217;ve occasionally come home and opened the front door only for Twigs to immediately dart outside&amp;#8230;  just so he could cross the front porch, stop at the nearest blade of grass, and bite it.  None of the other cats have ever shown any interest in grass, but every once in a great while, Twigs would just get a &lt;em&gt;hankering&lt;/em&gt;, and it&amp;#8217;s the only reason he&amp;#8217;s ever so much as attempted to leave the house.  (Thank&amp;nbsp;goodness.)&lt;/p&gt;
&lt;p&gt;The thing that hit hardest right after he died was the feeding routine.  Several of the cats eat storebought food, kept out of reach in a big dog cage we bought for this purpose, while Pearl and Twigs share homemade food.  For the last couple months, whenever I went to go open the cage to let the other cats in, Twigs would trot along with them!  He wouldn&amp;#8217;t actually go in the cage, and he&amp;#8217;d even slow down before getting to it (so the others would get ahead and it&amp;#8217;d be easy to keep him out), but he &lt;em&gt;acted&lt;/em&gt; like he belonged inside.  It was such a perfect reflection of his personality: he went after something he wanted, yet he stopped short of breaking the&amp;nbsp;rules.&lt;/p&gt;
&lt;p&gt;Twigs knew how to have a good time, too.  He &lt;em&gt;loved&lt;/em&gt; rollin&amp;#8217; around on carpet.  He&amp;#8217;d wriggle on his back, grab the carpet with his claws and pull himself along it, and clearly be having the time of his life.  Our Vegas home didn&amp;#8217;t have any carpeted floors, but we added a little carpeted platform to the stairs (so the cats wouldn&amp;#8217;t fall off!) and he had just as good a time on that.  Later we got some small cat trees with singular round platforms, and those had a carpet texture he loved as&amp;nbsp;well.&lt;/p&gt;
&lt;p&gt;Rollin&amp;#8217; around would put Twigs in a feisty mood, and he&amp;#8217;d reach out to smack anyone — cat, dog, or human — who came nearby.  Ash would make a game out of this: they&amp;#8217;d tap the floor nearby or the edge of the platform, then try to pull their hand away before Twigs &amp;#8220;got&amp;#8221; them.  Sometimes Twigs would make a very riled-up face but not try to get you, and you could wind him up a little more by performing the &amp;#8220;cat pat&amp;#8221; — lightly and repeatedly tapping his haunch with your fingertips.  You could &lt;em&gt;watch&lt;/em&gt; him get more rowdy in real time, and then the game was to stop before he suddenly rolled over and tried to grab your&amp;nbsp;hand.&lt;/p&gt;
&lt;p&gt;Our home near Seattle was just up the street from a big park, and on a couple occasions, Ash took Twigs out for a walk on a little leash.  On one such walk, while I was holding Twigs&amp;#8217;s leash, he suddenly &lt;em&gt;darted&lt;/em&gt; straight away from me and towards some underbrush!  The leash caught him, of course, but he was running so fast that it actually yanked him right off the ground and flipped him over.  (He was fine, albeit just as surprised as we&amp;nbsp;were!)&lt;/p&gt;
&lt;p&gt;(On another walk, Twigs stood right in front of Ash and made a huge &lt;em&gt;myeehhhh&lt;/em&gt; up at them, clearly indicating that he was Done With Outside For Now.  Poor baby.  Ash picked him up, wrapped him in their sweatshirt, and held him until we got home.  He really knew how to say exactly what he was&amp;nbsp;thinking.)&lt;/p&gt;
&lt;p&gt;Twigs played the typical cat games as well, when he felt like it — he might join in when we were playing string with Pearl, or teleport into the room when the laser pointer came out.  One of the last things Twigs played with was a tiny mouse toy, ripped and with its stuffing pouring out.  He sometimes liked to carry them around, roll around on the floor fighting them, then carry them somewhere else and do it again.  He had a surprising ferocity with toys at times: wild eyes and incredibly quick pounces!  It made me appreciate all the more how gentle he was with cats and&amp;nbsp;people.&lt;/p&gt;
&lt;p&gt;Once in a great while he&amp;#8217;d play fetch, repeatedly bringing the same toy (or twist-tie or something) back to Ash&amp;#8217;s feet so they could toss it and he could pounce it again.  I even have an old video of Twigs playing chase: Ash would dash down the hallway, Twigs would dart after them with an &lt;em&gt;intensely&lt;/em&gt; serious expression, Ash would yelp that Twigs &amp;#8220;caught&amp;#8221; them, and then they&amp;#8217;d run down the hallway the other way.  I don&amp;#8217;t think any other cat we&amp;#8217;ve had has really done that!  They&amp;#8217;ll run &lt;em&gt;away&lt;/em&gt; from us, but not try to chase us&amp;nbsp;around.&lt;/p&gt;
&lt;p&gt;(Ash put fantasy &amp;#8220;Luneko&amp;#8221; versions of all our cats in &lt;a href="https://eevee.itch.io/neon-phase"&gt;&lt;span class="caps"&gt;NEON&lt;/span&gt; &lt;span class="caps"&gt;PHASE&lt;/span&gt;&lt;/a&gt;, a little game we made a few years ago, and I was struck by how Branch Commander Twig&amp;#8217;s personality was so serious, when Twigs struck me as mostly lighthearted and friendly.  But then, I suppose Twigs &lt;em&gt;was&lt;/em&gt; very serious — about being lighthearted and&amp;nbsp;friendly.)&lt;/p&gt;
&lt;p&gt;&lt;div class="gallery"&gt;
&lt;a href="/media/2019-10-goodbye-twigs/twigs-walk-climbing-tree.jpg" class="photo" title="He did not make it very far up this tree. "&gt;&lt;img src="/media/2019-10-goodbye-twigs/twigs-walk-climbing-tree_m.jpg" alt="Twigs outside in a leash and sweater, attempting to climb a tree"&gt;&lt;/a&gt;
&lt;a href="/media/2019-10-goodbye-twigs/twigs-walk-headed-home.jpg" class="photo" title="This didn&amp;#x27;t last too long before Ash picked him up and carried him the rest of the way. "&gt;&lt;img src="/media/2019-10-goodbye-twigs/twigs-walk-headed-home_m.jpg" alt="Ash walking a leashed Twigs along a sidewalk"&gt;&lt;/a&gt;
&lt;a href="/media/2019-10-goodbye-twigs/twigs-rollin-around.jpg" class="photo" title="He looooved carpet. "&gt;&lt;img src="/media/2019-10-goodbye-twigs/twigs-rollin-around_m.jpg" alt="Twigs laying on his back on a carpeted floor, in mid-roll"&gt;&lt;/a&gt;
&lt;a href="/media/2019-10-goodbye-twigs/twigs-rollin-around-with-bracelet.jpg" class="photo" title="I think he started to play with that velcro loop, but then discovered the floor was better. "&gt;&lt;img src="/media/2019-10-goodbye-twigs/twigs-rollin-around-with-bracelet_m.jpg" alt="Twigs on his side, nuzzling the carpet, with a blue velcro loop around his wrist"&gt;&lt;/a&gt;
&lt;a href="/media/2019-10-goodbye-twigs/twigs-catform.jpg" class="photo" title="The cat platform, or catform, was just as good. "&gt;&lt;img src="/media/2019-10-goodbye-twigs/twigs-catform_m.jpg" alt="Twigs in mid-roll on a small platform with carpet attached to it"&gt;&lt;/a&gt;
&lt;a href="/media/2019-10-goodbye-twigs/twigs-fighting-cat-tree.jpg" class="photo" title="Sometimes he would pause in the middle of wrestling, um, the platform he was on. "&gt;&lt;img src="/media/2019-10-goodbye-twigs/twigs-fighting-cat-tree_m.jpg" alt="Twigs looking over the edge of a cat tree platform, his arm wrapped underneath and holding it"&gt;&lt;/a&gt;
&lt;a href="/media/2019-10-goodbye-twigs/twigs-feisting-paper-towels.jpg" class="photo" title="He has been caught in the act and does not care. "&gt;&lt;img src="/media/2019-10-goodbye-twigs/twigs-feisting-paper-towels_m.jpg" alt="Twigs clutching a roll of paper towels in both arms, his ears back in a feisty pose"&gt;&lt;/a&gt;
&lt;/div&gt;&lt;/p&gt;
&lt;p&gt;I can&amp;#8217;t tell what effect this has had on the other cats.  They were all friendly with Twigs.  Do they wonder where he is?  Do they, too, assume he&amp;#8217;s out of sight somewhere?  Are they grieving?  Will they grieve&amp;nbsp;later?&lt;/p&gt;
&lt;p&gt;The other cats got to saw Styx&amp;#8217;s body, but Twigs died elsewhere.  We have no way to tell them what happened to him.  They just have to&amp;#8230;  guess?  After living their whole lives with him?  That&amp;nbsp;sucks.&lt;/p&gt;
&lt;p&gt;I &lt;em&gt;think&lt;/em&gt; they&amp;#8217;ve been more affectionate over the past week or so.  Or they might be cuddling more because it&amp;#8217;s getting colder.  Or I might be paying more attention to them.  Hard to&amp;nbsp;say.&lt;/p&gt;
&lt;p&gt;They do seem to be expanding their roles to fill Twigs&amp;#8217;s niche.  Napoleon, best known for spending almost all his time alone, has come and hung out on the couch — virtually unheard of.  Anise and Cheeseball are, well, fighting each &lt;em&gt;other&lt;/em&gt; instead of both fighting Twigs — but they&amp;#8217;re starting fewer fights with Pearl.  Pearl, who has had absolutely no tolerance for Anise since we left Vegas, has spent whole nights asleep next to him without making a&amp;nbsp;fuss.&lt;/p&gt;
&lt;p&gt;I guess they learned a lot from&amp;nbsp;him.&lt;/p&gt;
&lt;p&gt;&lt;div class="gallery"&gt;
&lt;a href="/media/2019-10-goodbye-twigs/twigs-sphynx-wall.jpg" class="photo" title="This is so perfect.  I&amp;#x27;m surprised he&amp;#x27;s not in the middle. "&gt;&lt;img src="/media/2019-10-goodbye-twigs/twigs-sphynx-wall_m.jpg" alt="Twigs, Pearl, and Anise sat in similar poses all in a row along a couch arm"&gt;&lt;/a&gt;
&lt;a href="/media/2019-10-goodbye-twigs/twigs-pearl-in-cardboard-sauna.jpg" class="photo" title="We had a cardboard box set partly atop a heater vent for a while, to make a little cat sauna. "&gt;&lt;img src="/media/2019-10-goodbye-twigs/twigs-pearl-in-cardboard-sauna_m.jpg" alt="Twigs and Pearl lounging in a cardboard box"&gt;&lt;/a&gt;
&lt;a href="/media/2019-10-goodbye-twigs/twigs-pearl-sunbeam.jpg" class="photo" title="Pearl doesn&amp;#x27;t like to share her sunbeams, but sometimes she&amp;#x27;d allow Twigs. "&gt;&lt;img src="/media/2019-10-goodbye-twigs/twigs-pearl-sunbeam_m.jpg" alt="Twigs and Pearl lounging in a swivel chair near a sliding glass door, bathed in sunlight"&gt;&lt;/a&gt;
&lt;a href="/media/2019-10-goodbye-twigs/twigs-anise-camping.jpg" class="photo" title="I have no idea how they even both fit in there? "&gt;&lt;img src="/media/2019-10-goodbye-twigs/twigs-anise-camping_m.jpg" alt="Twigs and Anise both crammed into a little enclosed cat bed shaped like a tent, both their heads sticking out the front"&gt;&lt;/a&gt;
&lt;/div&gt;&lt;/p&gt;
&lt;p&gt;Twigs was also &lt;em&gt;fiercely&lt;/em&gt; loyal, but thankfully only had to show it a couple&amp;nbsp;times.&lt;/p&gt;
&lt;p&gt;We spent last summer in Marl&amp;#8217;s parents&amp;#8217; unused (finished) basement, where they kept four cats of their own.  (For a total of &lt;em&gt;nine&lt;/em&gt;.  We had quite a time.)  One of them, Seamus, kept antagonizing our only furry cat,&amp;nbsp;Napoleon.&lt;/p&gt;
&lt;p&gt;We aren&amp;#8217;t really sure how or why this started, but every so often, Seamus would start chasing Napoleon around, and Napoleon would &lt;em&gt;scream&lt;/em&gt;.  I don&amp;#8217;t know why Napoleon was so scared of him, or what Seamus thought he was doing, or why he couldn&amp;#8217;t understand that Napoleon didn&amp;#8217;t like it.  It was a constant source of stress for everyone; Seamus did it infrequently but seemingly on a whim, and we didn&amp;#8217;t have many options for segregating the cats&amp;nbsp;outright.&lt;/p&gt;
&lt;p&gt;The incredible thing was, every time Seamus would start chasing Napoleon&amp;#8230;  &lt;em&gt;Twigs&lt;/em&gt; would start chasing &lt;em&gt;Seamus&lt;/em&gt;.  And then Pearl would chase along with Twigs.  And this would often end with Twigs and Pearl facing Seamus down, with Twigs saying some &lt;em&gt;very&lt;/em&gt; nasty things that I will not repeat&amp;nbsp;here.&lt;/p&gt;
&lt;p&gt;(Anise would often show up and also run around, but he didn&amp;#8217;t seem to understand why everyone was making such a fuss.  While Twigs and Pearl were cornering Seamus, Anise would be standing next to them while mostly looking confused.  &lt;em&gt;Hey guys I see we&amp;#8217;re playing chase!!  I love chase too!!  Oh why&amp;#8217;d we all stop?&lt;/em&gt;)&lt;/p&gt;
&lt;p&gt;I wouldn&amp;#8217;t say it &lt;em&gt;helped&lt;/em&gt; matters much, but it was strangely heartwarming.  Twigs considered Napoleon his friend and had no problem telling this strange bully cat, a Maine Coon twice his size, to fuck right&amp;nbsp;off.&lt;/p&gt;
&lt;p&gt;Oh, but that&amp;#8217;s&amp;nbsp;nothing.&lt;/p&gt;
&lt;p&gt;Apollo, that German shepherd we used to have, once somehow managed to knock down a whole set of shelves in Ash&amp;#8217;s room.  Ash, of course, yelled his name in response.  They must&amp;#8217;ve sounded &lt;em&gt;really&lt;/em&gt; mad, because Twigs appeared &lt;em&gt;instantly&lt;/em&gt;.  He stood right in front of Apollo (separating him from Ash), in a very aggressive stance, making some very threatening growls and&amp;nbsp;meows.&lt;/p&gt;
&lt;p&gt;And he chased Apollo out of the room and right down the&amp;nbsp;hallway.&lt;/p&gt;
&lt;p&gt;All Twigs knew was that Apollo had seriously upset Ash, and that was that.  No questions asked.  This tiny little cat stood up to a giant wolf, because he thought Ash needed defending.  Twigs was never aggressive or mean towards Apollo any other time, before or since.  This only happened once, once &lt;em&gt;ever&lt;/em&gt;, when Twigs thought Ash was in&amp;nbsp;danger.&lt;/p&gt;
&lt;p&gt;What a brave cat!  If Apollo had wished Ash (or Twigs) harm, well, I don&amp;#8217;t like those odds.  But Twigs didn&amp;#8217;t even think twice.  We&amp;#8217;ve never stopped marvelling over&amp;nbsp;it.&lt;/p&gt;
&lt;p&gt;I say &amp;#8220;brave&amp;#8221; very deliberately, because Twigs while was not &lt;em&gt;fearless&lt;/em&gt;, he stood up to his fears.  The only one we really saw was a fear of, ah, foam strips.  See, we used to have a tiny &amp;#8220;gym&amp;#8221; in the corner of the kitchen, and the equipment sat on a foam mat made out of tiles with jigsaw edges that could fit together.  To give the assembled mat a smooth perimeter, the tiles also came with thin edge&amp;nbsp;pieces.&lt;/p&gt;
&lt;p&gt;Foot traffic (or cats) could knock one of the edge pieces loose, leaving a strip of black foam alone on the floor.  Twigs found this &lt;em&gt;highly alarming&lt;/em&gt;.  He would crouch down and eye it very suspiciously, creep up to give it a light smack and then back off, and generally treat it like a live wire.  We assume it looked like a snake to him, though no other cat took interest in the edge pieces except to play with them, and Twigs never reacted the same way to anything else&amp;nbsp;snake-shaped.&lt;/p&gt;
&lt;p&gt;But he didn&amp;#8217;t run away.  He investigated, to see if it was dangerous, see if there was a predator in his home.  Even after we&amp;#8217;d find him doing this and put the foam piece back, Twigs would creep around for a while, looking for possible snakes until he was convinced it was gone.  He was clearly very wary, yet he never ran, never&amp;nbsp;hid.&lt;/p&gt;
&lt;p&gt;The only other times I recall seeing Twigs anything close to scared were when he encountered a couple of accessories that resembled large animals: a Lucario hat Ash bought many years ago, and one of those goofy horse masks.  I&amp;#8217;m not even sure if &amp;#8220;scared&amp;#8221; is even the right word; he looked more annoyed?  He neither backed down nor tried to attack them.  I only remember him standing his ground and hissing, warning them to leave him&amp;nbsp;alone.&lt;/p&gt;
&lt;p&gt;I never heard him hiss any other&amp;nbsp;time.&lt;/p&gt;
&lt;p&gt;(Ash did, though.  Once as a tiny kitten, our late cat Granite sat on him.  A big furry cat just sat his ass right down on this little kitten.  Kitten Twigs hissed about this, but kittens aren&amp;#8217;t very ferocious hissers, so it came out &lt;em&gt;khh! khh!&lt;/em&gt;, which Granite ignored.Funnily enough, once Twigs grew up, he developed his own habit of sitting on furred&amp;nbsp;cats!)&lt;/p&gt;
&lt;p&gt;&lt;div class="gallery"&gt;
&lt;a href="/media/2019-10-goodbye-twigs/twigs-snuggling-granite.jpg" class="photo" title="Twigs and Granite made up later. "&gt;&lt;img src="/media/2019-10-goodbye-twigs/twigs-snuggling-granite_m.jpg" alt="Twigs snuggled against Granite&amp;#x27;s tummy"&gt;&lt;/a&gt;
&lt;a href="/media/2019-10-goodbye-twigs/twigs-sitting-on-napoleon.jpg" class="photo" title="He looks like he KNOWS this doesn&amp;#x27;t really work, and yet. "&gt;&lt;img src="/media/2019-10-goodbye-twigs/twigs-sitting-on-napoleon_m.jpg" alt="Twigs sat with his back half atop Napoleon, a grey and white furred cat"&gt;&lt;/a&gt;
&lt;a href="/media/2019-10-goodbye-twigs/twigs-sitting-on-anise.jpg" class="photo" title="I guess he never really got out of that habit. "&gt;&lt;img src="/media/2019-10-goodbye-twigs/twigs-sitting-on-anise_m.jpg" alt="Twigs sitting on a blanket, right on top of Anise"&gt;&lt;/a&gt;
&lt;a href="/media/2019-10-goodbye-twigs/twigs-sitting-on-pearl.jpg" class="photo" title="I cannot believe this actually happened. "&gt;&lt;img src="/media/2019-10-goodbye-twigs/twigs-sitting-on-pearl_m.jpg" alt="Twigs sitting with his front half propped up on Pearl, who&amp;#x27;s clearly smaller than him"&gt;&lt;/a&gt;
&lt;a href="/media/2019-10-goodbye-twigs/twigs-laying-on-fool-anise.jpg" class="photo" title="Both ridiculous. "&gt;&lt;img src="/media/2019-10-goodbye-twigs/twigs-laying-on-fool-anise_m.jpg" alt="Twigs laying partially on Anise, who is asleep on his back with his paws in the air"&gt;&lt;/a&gt;
&lt;a href="/media/2019-10-goodbye-twigs/twigs-anise-beanbag.jpg" class="photo" title="A chinrest is fine, too. "&gt;&lt;img src="/media/2019-10-goodbye-twigs/twigs-anise-beanbag_m.jpg" alt="Twigs and Anise napping on a beanbag; Twigs is resting his chin on Anise&amp;#x27;s back"&gt;&lt;/a&gt;
&lt;a href="/media/2019-10-goodbye-twigs/twigs-attacked-by-pearl.jpg" class="photo" title="Babysitting his niece, like a champ. "&gt;&lt;img src="/media/2019-10-goodbye-twigs/twigs-attacked-by-pearl_m.jpg" alt="Twigs making a pained expression as Pearl, a much smaller sphynx, bites the back of his neck"&gt;&lt;/a&gt;
&lt;/div&gt;&lt;/p&gt;
&lt;p&gt;We haven&amp;#8217;t had a death since Styx.  Twigs&amp;#8217;s best friend!  I never once expected Twigs would be the next to go.  Now Napoleon is the only one left of the original&amp;nbsp;crew.&lt;/p&gt;
&lt;p&gt;Ash moved in with me not long after adopting Twigs.  I don&amp;#8217;t think he was even a year old.  I knew him his entire adult life!  I lived with him longer than I&amp;#8217;ve lived with anyone, save my parents as a&amp;nbsp;kid.&lt;/p&gt;
&lt;p&gt;For so many years, it&amp;#8217;s been Ash and Twigs.  The inseparable duo, joined at the hip.  I knew it would end someday, but I was &lt;em&gt;so sure&lt;/em&gt; that day was much further off.  I thought he&amp;#8217;d be around for another five years at least, and secretly hoped he&amp;#8217;d make it another ten.  But we only got half of that.  He loved twice as hard, and his heart burned out far too&amp;nbsp;early.&lt;/p&gt;
&lt;p&gt;He had so much life left in him.  He played, he ran around, he wrestled (or, at least, was wrestled upon).  He was still growing, inventing new antics and new ways to interact with&amp;nbsp;us.&lt;/p&gt;
&lt;p&gt;It&amp;#8217;s been a strange experience.  I couldn&amp;#8217;t even absorb the factual knowledge of his death at first, even as I spent much of the first few days crying.  How could &lt;em&gt;Twigs&lt;/em&gt; die?  That doesn&amp;#8217;t make any sense; I haven&amp;#8217;t seen him yet today, but he&amp;#8217;ll show up soon.  But I feel really sad.  Oh, right, that&amp;#8217;s because Twigs died.  Rinse, repeat, over and&amp;nbsp;over.&lt;/p&gt;
&lt;p&gt;We picked his ashes a few days later.  It&amp;#8217;s been nice to have him home again, and it helps to have something physical to look at, rather than just the lack of his presence.  Ash intends to paint his&amp;nbsp;urn.&lt;/p&gt;
&lt;p&gt;It got easier much more quickly than I expected, and that&amp;#8217;s been weird as well.  I wanted to hold onto his memory and be happy for the time I got to spend with him, and then that actually &lt;em&gt;happened&lt;/em&gt;.  I think about him a lot (especially over the multiple days it&amp;#8217;s taken to write this), and a lot of little things remind me of him, but they don&amp;#8217;t make me break down in tears.&amp;nbsp;Usually.&lt;/p&gt;
&lt;p&gt;That feels a &lt;em&gt;little&lt;/em&gt; bad.  But I know that hurting less doesn&amp;#8217;t mean I loved him any less.  And I know the last thing Twigs would want is for us to be&amp;nbsp;sad.&lt;/p&gt;
&lt;p&gt;&lt;div class="gallery"&gt;
&lt;a href="/media/2019-10-goodbye-twigs/twigs-styx-stalking.jpg" class="photo" title="He is definitely coming for you, Styx. "&gt;&lt;img src="/media/2019-10-goodbye-twigs/twigs-styx-stalking_m.jpg" alt="Styx on a bed looking at Twigs, who&amp;#x27;s in the background, crouched down with wide eyes and staring at Styx"&gt;&lt;/a&gt;
&lt;a href="/media/2019-10-goodbye-twigs/twigs-styx-standoff.jpg" class="photo" title="This is about as intense as fighting with Styx got. "&gt;&lt;img src="/media/2019-10-goodbye-twigs/twigs-styx-standoff_m.jpg" alt="Twigs and Styx facing each other; Twigs has his ears back, and Styx has one paw lifted in preparation for smacking him"&gt;&lt;/a&gt;
&lt;a href="/media/2019-10-goodbye-twigs/twigs-caught-grooming-styx-tail.jpg" class="photo" title="Caught in the act, licking Styx&amp;#x27;s tail.  I&amp;#x27;m not sure he ever did this with another cat. "&gt;&lt;img src="/media/2019-10-goodbye-twigs/twigs-caught-grooming-styx-tail_m.jpg" alt="Twigs looking at the camera in possible surprise, with Styx&amp;#x27;s tail between his paws"&gt;&lt;/a&gt;
&lt;a href="/media/2019-10-goodbye-twigs/twigs-myeeehhh.jpg" class="photo" title="Myyeeehhhhhh "&gt;&lt;img src="/media/2019-10-goodbye-twigs/twigs-myeeehhh_m.jpg" alt="Twigs meowing at the camera, his lip and nose wrinkled"&gt;&lt;/a&gt;
&lt;/div&gt;&lt;/p&gt;
&lt;p&gt;Twigs was the best.  I miss so much about him.  I miss the way his whole nose scrunched up when he did a big meow.  I miss his distinct little trot as he came down the hallway to see you.  I miss watching him do eager little circles on the floor as I got the food out.  I miss how he&amp;#8217;d smack his lips as he showed up, a distinct and inexplicable quirk I&amp;#8217;ve never seen in any other cat, a good compliment to how long he&amp;#8217;d spend licking his chops after eating.  I miss his huge ears!  I miss &amp;#8220;savannah cat&amp;#8221; — when he&amp;#8217;d hook his paws over the edge of something he was lying on, like an arm or the edge of a cat bed or the corner of my computer tower.  I miss what a serene and calming presence he&amp;nbsp;was.&lt;/p&gt;
&lt;p&gt;It&amp;#8217;s funny how some of the most memorable moments are things he only did one time.  He joined Ash in the bathtub once — they were reading a book and Twigs came in, hopped in the bath, and sat in water up to his neck, just to be with them.  He often announced his presence with a questioning meow when coming into Ash&amp;#8217;s Vegas room at night, and once he did this really funny &amp;#8220;meow-ow!&amp;#8221; kind of double meow, and we&amp;#8217;ve repeated it to each other as a nod to Twigs ever since, even though he never did it&amp;nbsp;again.&lt;/p&gt;
&lt;p&gt;One fall, we got a tiny pumpkin — the size of a slightly disappointing donut — and Twigs was &lt;em&gt;enamored&lt;/em&gt; with it.  We&amp;#8217;d roll it along its edge and he&amp;#8217;d chase after it and keep biting it, and it was &lt;em&gt;so&lt;/em&gt; cute.  Another fall, we bought another one, and Twigs wasn&amp;#8217;t interested in it at all.  Very cutting-edge of him.  Tiny pumpkin is &lt;em&gt;so&lt;/em&gt; last&amp;nbsp;year.&lt;/p&gt;
&lt;p&gt;He used to be really interested in eggs, too.  For a while, we couldn&amp;#8217;t turn our backs on an egg on the counter, because Twigs would materialize and start gently batting it around.  Then he lost&amp;nbsp;interest.&lt;/p&gt;
&lt;p&gt;I miss how he slept with me.  He&amp;#8217;d always slept either behind my knees or on top of the covers, but right towards the end of his life, he invented a new trick, just for me.  I sleep on my side, so he couldn&amp;#8217;t lay on my chest; instead, he went under the covers, poked his head out, and lay against my chest with his head on my pillow.  Like a little person!  It was so sweet.  He&amp;#8217;d then keep nuzzling my face with his cold wet nose, which was kind of annoying.  I miss that,&amp;nbsp;too.&lt;/p&gt;
&lt;p&gt;Even the annoying things are conspicuously absent.  He frequently stepped on my hair while I was in bed, trying to get around me to get to Ash, and &lt;em&gt;wow&lt;/em&gt; that is painful.  Twigs groomed his cat sweaters more intensely than any other cat, biting the fabric and pulling so hard that it stretched and made this horrible high-pitched squeak, like nails on a chalkboard.  He loved to groom people, too — usually on the chin or upper chest, since that&amp;#8217;s what was accessible when he lay on you.  Somehow Ash got used to it (and learned to redirect him to their palm, which he&amp;#8217;d lick for &lt;em&gt;ages&lt;/em&gt;), but I could never bear more than a few seconds of his cheese grater&amp;nbsp;tongue.&lt;/p&gt;
&lt;p&gt;What a good&amp;nbsp;cat.&lt;/p&gt;
&lt;p&gt;&lt;div class="gallery"&gt;
&lt;a href="/media/2019-10-goodbye-twigs/twigs-bathtub.jpg" class="photo" title="He just hopped right in there like it was nothing. "&gt;&lt;img src="/media/2019-10-goodbye-twigs/twigs-bathtub_m.jpg" alt="Twigs sitting casually in a half-filled bathtub"&gt;&lt;/a&gt;
&lt;a href="/media/2019-10-goodbye-twigs/twigs-savannah-cat-tower.jpg" class="photo" title="Savannah cat! "&gt;&lt;img src="/media/2019-10-goodbye-twigs/twigs-savannah-cat-tower_m.jpg" alt="Twigs laying atop my computer tower with his paws hooked over the corner"&gt;&lt;/a&gt;
&lt;a href="/media/2019-10-goodbye-twigs/twigs-biting-string.jpg" class="photo" title="Ash cobbled together this toy from a tea light candle and some ribbon, but Twigs was mostly interested in the string. "&gt;&lt;img src="/media/2019-10-goodbye-twigs/twigs-biting-string_m.jpg" alt="Twigs biting a string with a toy on the end of it, his teeth visible and lip wrinkled"&gt;&lt;/a&gt;
&lt;a href="/media/2019-10-goodbye-twigs/twigs-grabbing-toy.jpg" class="photo" title="Not only using it as intended, but with both hands! "&gt;&lt;img src="/media/2019-10-goodbye-twigs/twigs-grabbing-toy_m.jpg" alt="Twigs on a cat tree, grasping the toy from the previous photo in both paws and also biting it"&gt;&lt;/a&gt;
&lt;/div&gt;&lt;/p&gt;
&lt;p&gt;I felt like I&amp;#8217;d been waiting for this all year.  I don&amp;#8217;t want to go much into it, but death has felt like a looming spectre almost since we moved in.  The pointlessness of doing things, the feeling that I&amp;#8217;m just passing time waiting to die, the occasional intrusive thought about a tragic accident befalling one of us or one of the cats.  Never Twigs,&amp;nbsp;though.&lt;/p&gt;
&lt;p&gt;Last year was harder on me than I thought.  I fired on all cylinders, trying to get Ash back on their feet, and once that happened&amp;#8230;  I deflated and never quite recovered.  I lost a lot of my drive, my spark, my voice.  I got frustrated with difficult work much more easily.  I stopped writing.  I stopped interacting.  I stopped &lt;em&gt;trying&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;I didn&amp;#8217;t even realize.  Even as I felt increasingly distant and detached from the universe, I still thought I&amp;#8217;d been pretty normal all year with only a few rough patches.  It&amp;#8217;s been hard to compare the past to the present, separated as they are by a strange and tumultuous six months that changed almost everything.  Then Ash commented that I&amp;#8217;d seemed kind of down all year.  What a jolt that was, and only a few days before Twigs&amp;nbsp;died.&lt;/p&gt;
&lt;p&gt;Twigs&amp;#8217;s death feels like a kick in the ass.  I&amp;#8217;ve felt a lot of despair over the past year, but all of it has been tied to anxieties and what-ifs — imaginary things.  But this is &lt;em&gt;sad&lt;/em&gt;, which is very different.  This carries a pain for something tangible, something &lt;em&gt;real&lt;/em&gt;, something important, something I want to hold onto.  How can any of my little fantasy fears matter, when the loss of a cat outweighs all of them&amp;nbsp;combined?&lt;/p&gt;
&lt;p&gt;I don&amp;#8217;t want to waste any more time.  I want to reflect what I admired about Twigs: kind, patient, confident, and loving.  I want to make this mean&amp;nbsp;something.&lt;/p&gt;
&lt;p&gt;&lt;div class="gallery"&gt;
&lt;a href="/media/2019-10-goodbye-twigs/twigs-styx-twins.jpg" class="photo" title="Twigs&amp;#x27;s and Styx&amp;#x27;s matching sweaters! "&gt;&lt;img src="/media/2019-10-goodbye-twigs/twigs-styx-twins_m.jpg" alt="Twigs and Styx curled up next to each other, in identical poses, wearing identical sweaters"&gt;&lt;/a&gt;
&lt;a href="/media/2019-10-goodbye-twigs/twigs-croissant.jpg" class="photo" title="The classic croissant. "&gt;&lt;img src="/media/2019-10-goodbye-twigs/twigs-croissant_m.jpg" alt="Twigs curled up on a bed with all his paws tucked under his chin"&gt;&lt;/a&gt;
&lt;a href="/media/2019-10-goodbye-twigs/twigs-medium-rare.jpg" class="photo" title="Look how medium rare he is! "&gt;&lt;img src="/media/2019-10-goodbye-twigs/twigs-medium-rare_m.jpg" alt="Twigs laying on a sunny windowsill, so warm that he&amp;#x27;s turned a little pink"&gt;&lt;/a&gt;
&lt;/div&gt;&lt;/p&gt;
&lt;p&gt;Twigs had a good life.  He spent it around people and cats he loved dearly, and who loved him right back.  He had friends when he was lonely and blankets when he was&amp;nbsp;chilly.&lt;/p&gt;
&lt;p&gt;Oh, did he ever love blankets.  Sphynxes are naked and tend to seek out warmth, of course, but of the four we&amp;#8217;ve had, Twigs was by far the one who treated heat sources like a &lt;em&gt;passion&lt;/em&gt; rather than mere physical comfort.  His ability to identify the most snuggly spot to back his ass into was nothing short of superfeline.  Sometimes he&amp;#8217;d toast himself so well that he turned a little pink!  And he used to do this incredible display of cat paws, with &lt;em&gt;all four&lt;/em&gt; paws, accompanied by the occasional meow — but only on a specific blanket that we&amp;#8217;ve long since&amp;nbsp;lost.&lt;/p&gt;
&lt;p&gt;He was also the one who tolerated cat sweaters the best (despite inflicting the most destruction on them).  Anise&amp;#8217;s powers of antagonism are greatly reduced in a sweater, and he will &lt;em&gt;run away&lt;/em&gt; if he sees you approaching him with one; Pearl still does a funny awkward walk with her back half lower to the ground, even after wearing them through half a dozen winters.  But Twigs in a sweater just acted like&amp;nbsp;Twigs.&lt;/p&gt;
&lt;p&gt;And what a well-travelled cat!  He lived in four states and drove through half a dozen others.  That&amp;#8217;s more of the world than a decent number of humans see.  He got to meet and snuggle with all kinds of other cats, and even some sort of giant wolf-cat who tried to herd him occasionally.  He got to see the great outdoors, then decided he didn&amp;#8217;t like it and returned to the great&amp;nbsp;indoors.&lt;/p&gt;
&lt;p&gt;Twigs did spend a couple of his later years afflicted with &amp;#8220;pillow paw&amp;#8221; — his pawpads swelled up one day, for seemingly no reason.  Our vet couldn&amp;#8217;t find an underlying cause, and meanwhile it was uncomfortable for him to land on his feet from a height.  Poor guy.  I&amp;#8217;m eternally grateful to the vet we found last summer, who finally solved the mystery and cured him.  He got to spend his final year active and unhindered&amp;nbsp;again.&lt;/p&gt;
&lt;p&gt;Ash spent much of our last couple Vegas years secluded in their office, too, so Twigs didn&amp;#8217;t get as much face time as I&amp;#8217;m sure he would&amp;#8217;ve liked.  But in our new place, both of our desks are out in the open and right next to each other, so Twigs could see them whenever he wanted.  Sometimes he lay on a cat bed on my desk watching them, or strolled back and forth between us both, purring up a&amp;nbsp;storm.&lt;/p&gt;
&lt;p&gt;It&amp;#8217;s been a bit of a rollercoaster for all of us, but I think the last year was the best year of his&amp;nbsp;life.&lt;/p&gt;
&lt;p&gt;&lt;div class="gallery"&gt;
&lt;a href="/media/2019-10-goodbye-twigs/twigs-styx-asleep-in-hammock.jpg" class="photo" title="Best friends forever. "&gt;&lt;img src="/media/2019-10-goodbye-twigs/twigs-styx-asleep-in-hammock_m.jpg" alt="Twigs and Styx both napping on the same blanket but at different &amp;#x27;levels&amp;#x27;, and both posed as though in a hammock"&gt;&lt;/a&gt;
&lt;a href="/media/2019-10-goodbye-twigs/twigs.jpg" class="photo" title="One of my more recent photos of Twigs, in his natural environment. "&gt;&lt;img src="/media/2019-10-goodbye-twigs/twigs_m.jpg" alt="Closeup of Twigs&amp;#x27;s slightly frumpy face, as he sits on a blanket while wearing a sweater"&gt;&lt;/a&gt;
&lt;a href="/media/2019-10-goodbye-twigs/twigs-final.jpg" class="photo" title="The last photo I ever took of Twigs, surrounded by friends. "&gt;&lt;img src="/media/2019-10-goodbye-twigs/twigs-final_m.jpg" alt="Twigs and Pearl sitting in a cat tree and illuminated by a sunbeam, with Anise and Cheeseball on different platforms in the background"&gt;&lt;/a&gt;
&lt;a href="/media/2019-10-goodbye-twigs/twigs-goodbye.jpg" class="photo" title="Twigs, in his usual spot.  See you around. "&gt;&lt;img src="/media/2019-10-goodbye-twigs/twigs-goodbye_m.jpg" alt="A paper cutout of Twigs&amp;#x27;s head, propped up at the top of a cat tree"&gt;&lt;/a&gt;
&lt;/div&gt;&lt;/p&gt;
&lt;p&gt;I miss Twigs, but I smile when I think about him.  He made us so happy while he was&amp;nbsp;here.&lt;/p&gt;
&lt;p&gt;Twigs came into Ash&amp;#8217;s life while they were somewhat adrift — no clear goals, no home of their own, resigned to an unhappy marriage.  He stuck with them for nine whole years, unwavering in his affection.  He followed them down into the darkness, down where they couldn&amp;#8217;t feel love from anyone — anyone except&amp;nbsp;Twigs.&lt;/p&gt;
&lt;p&gt;Now Ash has work and a community they love.  We have a home together, and it finally feels like one.  And by sheer coincidence, Ash&amp;#8217;s divorce was finalized mere days after Twigs died.  His entire life was contained within that marriage, from birth to&amp;nbsp;death.&lt;/p&gt;
&lt;p&gt;(Oh, we&amp;#8217;re married now.&amp;nbsp;Hurrah.)&lt;/p&gt;
&lt;p&gt;Ash adopted Twigs almost on a whim, and he left us just as abruptly.  As though he&amp;#8217;d only shown up in the first place to help Ash when they needed it, and with Marl finally out of our lives, his work here was&amp;nbsp;done.&lt;/p&gt;
&lt;p&gt;The last thing Twigs did, the night that he died, was tell us he loved us.  Ash put him under the blanket to try warming him up, and at first he was by our feet&amp;#8230;  but then he crawled up to slump against me, similar to how he did when I was alone in bed, and then he climbed on Ash&amp;#8217;s chest and lay on them for a moment.  Right at the end, as cold and confused as he must&amp;#8217;ve felt, all he wanted was to be with Ash, to be with both of&amp;nbsp;us.&lt;/p&gt;
&lt;p&gt;I don&amp;#8217;t know where Twigs is, now.  He might be nowhere.  But the universe has consistently proven itself to be more baffling and beautiful than I expect, so I&amp;#8217;ll hold out hope that he&amp;#8217;s somewhere — somewhere he can once again see Styx, his (other) best friend in the whole wide world.  Somewhere that we can see them both again, one&amp;nbsp;day.&lt;/p&gt;
&lt;p&gt;Goodbye, Twigs!  We&amp;#8217;ll always love you, and we&amp;#8217;ll always miss&amp;nbsp;you.&lt;/p&gt;
&lt;p&gt;Thank you, so much, for&amp;nbsp;everything.&lt;/p&gt;
&lt;p&gt;&lt;div class="prose-full-illustration"&gt;&lt;img src="/media/2019-10-goodbye-twigs/twigs-portrait.jpg" alt="A colorful and abstract painting of Twigs"&gt;&lt;/div&gt;&lt;/p&gt;</content><category term="blog"></category><category term="cats"></category><category term="twigs"></category><category term="personal"></category><category term="eulogy"></category></entry><entry><title>Eevee gained 2977 experience points</title><link href="https://eev.ee/blog/2019/01/14/eevee-gained-2977-experience-points/" rel="alternate"></link><published>2019-01-14T21:16:00-08:00</published><updated>2019-01-14T21:16:00-08:00</updated><author><name>Eevee</name></author><id>tag:eev.ee,2019-01-14:/blog/2019/01/14/eevee-gained-2977-experience-points/</id><summary type="html">&lt;p&gt;Eevee grew to level 32!&lt;/p&gt;
&lt;p&gt;This has been a surreal and difficult year, but everything turned out &lt;em&gt;much&lt;/em&gt; better in the end.&lt;/p&gt;
</summary><content type="html">&lt;p&gt;Eevee grew to level&amp;nbsp;32!&lt;/p&gt;
&lt;p&gt;This has been a surreal and difficult year, but everything turned out &lt;em&gt;much&lt;/em&gt; better in the&amp;nbsp;end.&lt;/p&gt;


&lt;p&gt;I can&amp;#8217;t possibly do the whole story justice, and I&amp;#8217;m not eager to rehash it anyway, so here&amp;#8217;s the incredibly short version.  The players are myself, my partner Ash (formerly Mel, aka glip), and their (at the time) husband&amp;nbsp;Marl.&lt;/p&gt;
&lt;p&gt;Helpful context: for years, Ash has been the target of a stalking slash gossip campaign.  A group of folks &lt;a href="http://nymag.com/intelligencer/2016/07/kiwi-farms-the-webs-biggest-community-of-stalkers.html"&gt;on a forum infamous for this sort of thing&lt;/a&gt; likes to dig through our online footprints for dirt and compile lengthy lists of awful things we&amp;#8217;ve allegedly done.  Every time this happened, we dropped everything and investigated.  It&amp;#8217;s exhausting.  Virtually everything we&amp;#8217;ve been accused of has been some combination of long since resolved, wildly embellished, carefully trimmed to remove any explanatory context, completely misunderstood, distorted through rounds of telephone, or occasionally outright fabricated — and what&amp;#8217;s not, we gladly apologize for and try to repair.  But there are &lt;a href="http://rationalwiki.org/wiki/Gish_Gallop"&gt;so many&lt;/a&gt; fractalline complaints that no casual observer could possibly double-check the evidence (it sometimes takes weeks for &lt;em&gt;us&lt;/em&gt; to comb through it all), and we can&amp;#8217;t respond effectively without producing a massive tome that no one will bother to&amp;nbsp;read.&lt;/p&gt;
&lt;p&gt;This is where we&amp;#8217;re &lt;em&gt;starting&lt;/em&gt;&amp;nbsp;from.&lt;/p&gt;
&lt;p&gt;In early April, someone posted logs from 2012 of Marl having horny chats with someone who was 15/16 and suggesting a variety of other shady behavior.  The teenager in question was someone Marl had briefly hired to help him assemble con merch; Ash and I had barely interacted with her at all and didn&amp;#8217;t even know they&amp;#8217;d spoken outside of that.  Nevertheless, &amp;#8220;warnings&amp;#8221; about &lt;em&gt;all three of us&lt;/em&gt; began to circulate rapidly, Ash&amp;#8217;s friends started getting doxxed, and folks bailed on us in droves — all while Ash and I were still trying to grasp what was even going&amp;nbsp;on.&lt;/p&gt;
&lt;p&gt;Marl offered a general apology, told us the logs were bogus, then became upset and withdrew.  He didn&amp;#8217;t keep logs of his own, so we had little else to go on and had to trust him.  I found some oddities in the logs: enough to make me skeptical of them and more trusting of Marl, but nothing&amp;nbsp;concrete.&lt;/p&gt;
&lt;p&gt;Ash was completely exhausted with this, which was by no means the first accusation leveled at &lt;em&gt;them&lt;/em&gt; over events they hadn&amp;#8217;t even known about.  They couldn&amp;#8217;t take any more, were on the verge of a breakdown, and decided to abandon the internet altogether.  That left &lt;em&gt;me&lt;/em&gt; as the obvious conduit for anyone trying to get at Ash, and I am very bad at &lt;em&gt;not&lt;/em&gt; grouching about something annoying, so this presented a very tangible risk.  Ash is more important to me than being online, so I left as&amp;nbsp;well.&lt;/p&gt;
&lt;p&gt;For various reasons, not least of which is that the forum had our address and was still whipping a rather lot of people into a bloodthirsty frenzy, we no longer felt safe in our home.  We left that&amp;nbsp;too.&lt;/p&gt;
&lt;p&gt;We stayed with Marl&amp;#8217;s parents for a while, which gave Ash time to think.  They started to feel the full weight of a lot of things, big and small, that Marl had done over the course of their ten-year marriage: lots of breaches of trust; stretching Ash&amp;#8217;s patience as far as it would go and then promising to improve for just long enough; leaving us to deal with accusations levelled against him with zero information &lt;em&gt;more than once&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;He also eventually admitted that the logs were &lt;em&gt;not&lt;/em&gt; entirely bogus, although he never clarified more specifically, so I have no way to know what he actually did or not.  At the very least, he did slide into the DMs of a high schooler (who was also his employee, no&amp;nbsp;less).&lt;/p&gt;
&lt;p&gt;We subsequently evicted him from our lives, leaving him with his parents when we moved to a new&amp;nbsp;place.&lt;/p&gt;
&lt;hr /&gt;
&lt;p&gt;I&amp;#8217;m told the teenager dropped off the forum (which she&amp;#8217;d been posting on anonymously), and no one but Marl knows her identity, so she&amp;#8217;s effectively vanished.  We haven&amp;#8217;t had contact with Marl in months.  That just leaves&amp;nbsp;us.&lt;/p&gt;
&lt;p&gt;I&amp;#8217;ve explained a lot of this in gratuitous detail on Twitter, and it&amp;#8217;s been relatively quiet for a while now, but the initial confused mess can&amp;#8217;t be undone.  Gossip cannot be un-spread.  To this day, we still get folks trying to warn people away from us, based primarily on what Marl did behind our&amp;nbsp;backs.&lt;/p&gt;
&lt;p&gt;Oh, well.  Can&amp;#8217;t please everyone, right?  Does that actually apply&amp;nbsp;here?&lt;/p&gt;
&lt;p&gt;It drives me nuts to be misrepresented, but on the other hand, maybe it&amp;#8217;s okay that people who take gossip at face value are self-selecting themselves out of my internet&amp;nbsp;experience.&lt;/p&gt;
&lt;p&gt;Anyway, that&amp;#8217;s why my output was a bit low last year: I was chased from my home and thought I would be leaving the internet forever!  Then I had to spend a few months getting settled.  Plus I&amp;#8217;ve been on and off &lt;span class="caps"&gt;ADHD&lt;/span&gt; meds since May, which has kind of thrown me for a loop, but I finally got that all sorted out just a few days ago.  Now I can finally get back to, um, whatever it is that I&amp;nbsp;do.&lt;/p&gt;
&lt;hr /&gt;
&lt;p&gt;&lt;em&gt;In lighter&amp;nbsp;news!&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;We live in Colorado Springs now!  It&amp;#8217;s beautiful and lovely and actually has &lt;em&gt;weather&lt;/em&gt;, which is a nice change after five years in&amp;nbsp;Vegas.&lt;/p&gt;
&lt;p&gt;I changed my name!  It was in part to stay out of public records so we wouldn&amp;#8217;t be doxxed again, but then they doxxed the name change, so, that didn&amp;#8217;t work.  Oh, well, I&amp;#8217;m still happy I did it.  I&amp;#8217;m Evelyn Woods now.  That&amp;#8217;s right: I legally changed my name to&amp;nbsp;Eevee.&lt;/p&gt;
&lt;p&gt;Ash and I are engaged!  Also I love them a lot.  Marl injected a lot of invisible, ambiguous tension into the household; without that smothering us, we are &lt;em&gt;flourishing&lt;/em&gt;.  We went through hell together and made it out the other side.  I&amp;#8217;m…  well, I&amp;#8217;m really&amp;nbsp;happy.&lt;/p&gt;
&lt;p&gt;We got a new cat: Cheeseball, a Lykoi!  He loves to make friends and also fight them, and &lt;a href="https://www.youtube.com/watch?v=D6WMrE21oAQ"&gt;his antics&lt;/a&gt; helped a lot over the summer.  He&amp;#8217;s very&amp;nbsp;good.&lt;/p&gt;
&lt;p&gt;So good, in fact, that over the summer I started working on &lt;a href="https://github.com/eevee/anise-cheezball-rising"&gt;Cheezball Rising&lt;/a&gt;, a game about Cheeseball for the Game Boy Color!  &lt;em&gt;It is hard and I am not very far along.&lt;/em&gt;  Also I&amp;#8217;ve been in outer space and haven&amp;#8217;t worked on it much in several months.  But I&amp;#8217;ve been &lt;a href="https://eev.ee/everything/tags/cheezball-rising/"&gt;blogging the whole thing&lt;/a&gt; which is at least moderately&amp;nbsp;interesting.&lt;/p&gt;
&lt;p&gt;I also wrote a &lt;a href="https://github.com/eevee/fox-flux-advance"&gt;stub of a game&lt;/a&gt; for the &lt;span class="caps"&gt;GBA&lt;/span&gt; &lt;em&gt;in Rust&lt;/em&gt; over the past week for a game jam, though it hasn&amp;#8217;t gotten especially far&amp;nbsp;either.&lt;/p&gt;
&lt;p&gt;And, some other games?  Probably?  I think Alice&amp;#8217;s Day Off was this most recent February, right?  God, that feels like it was a decade ago.  So much for finishing it by&amp;nbsp;June.&lt;/p&gt;
&lt;p&gt;I kinda-sorta kept up with art over the summer, but art requires a certain kind of mood for me, and I…  wasn&amp;#8217;t in it.  Which is a shame, because I was starting to feel like I was getting&amp;nbsp;somewhere.&lt;/p&gt;
&lt;p&gt;I slopped together little Pelican-based art galleries for my &lt;a href="https://t.eev.ee/"&gt;&lt;span class="caps"&gt;SFW&lt;/span&gt;&lt;/a&gt; and &lt;a href="https://d.eev.ee/"&gt;&lt;span class="caps"&gt;NSFW&lt;/span&gt;&lt;/a&gt; art, which I&amp;#8217;d been meaning to do for a&amp;nbsp;while!&lt;/p&gt;
&lt;p&gt;I don&amp;#8217;t know.  I stopped tracking what I was doing every day quite so closely, since I wasn&amp;#8217;t doing much every day for a while there.  Maybe I&amp;#8217;ll start the weekly roundup posts back up?  Did anyone read&amp;nbsp;those?&lt;/p&gt;
&lt;hr /&gt;
&lt;p&gt;What about 2019,&amp;nbsp;then?&lt;/p&gt;
&lt;p&gt;I feel &lt;em&gt;unleashed&lt;/em&gt; and am absolutely certain this will be a fantastic year.  Mostly I have to catch up on everything I didn&amp;#8217;t do &lt;em&gt;last&lt;/em&gt; year.  Well, that&amp;#8217;s fine.  Let&amp;#8217;s see, what do I even have in the air right&amp;nbsp;now:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Cheezball Rising, the &lt;span class="caps"&gt;GBC&lt;/span&gt;&amp;nbsp;game&lt;/li&gt;
&lt;li&gt;fox flux advance, the &lt;span class="caps"&gt;GBA&lt;/span&gt; game, &lt;em&gt;maybe&lt;/em&gt;&lt;/li&gt;
&lt;li&gt;fox flux, the continuation of the desktop&amp;nbsp;game&lt;/li&gt;
&lt;li&gt;Alice&amp;#8217;s Day Off, which was only released as a&amp;nbsp;demo&lt;/li&gt;
&lt;li&gt;idchoppers, the Rust Doom&amp;nbsp;tool&lt;/li&gt;
&lt;li&gt;art, writing,&amp;nbsp;music&lt;/li&gt;
&lt;li&gt;idk half a dozen other things,&amp;nbsp;god&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;So, the usual: &lt;em&gt;make stuff&lt;/em&gt;.&lt;/p&gt;
&lt;!-- stick this down here to keep it out of the preview --&gt;
&lt;p&gt;&lt;audio src="/media/2012-01/levelup.ogv" controls autoplay&gt;&lt;/p&gt;</content><category term="blog"></category><category term="birthday"></category><category term="personal"></category></entry><entry><title>Cheezball Rising: Collision detection, part 1</title><link href="https://eev.ee/blog/2018/11/28/cheezball-rising-collision-detection-part-1/" rel="alternate"></link><published>2018-11-28T20:04:00-08:00</published><updated>2018-11-28T20:04:00-08:00</updated><author><name>Eevee</name></author><id>tag:eev.ee,2018-11-28:/blog/2018/11/28/cheezball-rising-collision-detection-part-1/</id><summary type="html">&lt;p&gt;This is a series about &lt;a href="https://github.com/eevee/anise-cheezball-rising"&gt;&lt;strong&gt;Star Anise Chronicles: Cheezball Rising&lt;/strong&gt;&lt;/a&gt;, an expansive adventure game about my cat for the Game Boy Color.  Follow along as I struggle to make something with this bleeding-edge console!&lt;/p&gt;
&lt;p&gt;GitHub has &lt;a href="https://github.com/eevee/anise-cheezball-rising/releases"&gt;intermittent prebuilt ROMs&lt;/a&gt;, or you can get them a week early &lt;a href="https://www.patreon.com/eevee/posts?tag=cheezball%20rising"&gt;on Patreon&lt;/a&gt; if you pledge $4.  More details in the &lt;a href="https://github.com/eevee/anise-cheezball-rising"&gt;&lt;span class="caps"&gt;README&lt;/span&gt;&lt;/a&gt;!&lt;/p&gt;
&lt;hr/&gt;
&lt;p&gt;In this issue, I bash my head against a rock.  Sorry, I mean I bash Star Anise against a rock.  It’s about collision detection.&lt;/p&gt;
&lt;p&gt;Previously: &lt;a href="https://eev.ee/blog/2018/10/09/cheezball-rising-opening-a-dialogue/"&gt;I draw some text to the screen&lt;/a&gt;.&lt;br/&gt;
Next: &lt;em&gt;more&lt;/em&gt; collision detection, and fixed-point arithmetic.&lt;/p&gt;
</summary><content type="html">&lt;p&gt;This is a series about &lt;a href="https://github.com/eevee/anise-cheezball-rising"&gt;&lt;strong&gt;Star Anise Chronicles: Cheezball Rising&lt;/strong&gt;&lt;/a&gt;, an expansive adventure game about my cat for the Game Boy Color.  Follow along as I struggle to make something with this bleeding-edge&amp;nbsp;console!&lt;/p&gt;
&lt;p&gt;GitHub has &lt;a href="https://github.com/eevee/anise-cheezball-rising/releases"&gt;intermittent prebuilt ROMs&lt;/a&gt;, or you can get them a week early &lt;a href="https://www.patreon.com/eevee/posts?tag=cheezball%20rising"&gt;on Patreon&lt;/a&gt; if you pledge $4.  More details in the &lt;a href="https://github.com/eevee/anise-cheezball-rising"&gt;&lt;span class="caps"&gt;README&lt;/span&gt;&lt;/a&gt;!&lt;/p&gt;
&lt;hr /&gt;
&lt;p&gt;In this issue, I bash my head against a rock.  Sorry, I mean I bash Star Anise against a rock.  It&amp;#8217;s about collision&amp;nbsp;detection.&lt;/p&gt;
&lt;p&gt;Previously: &lt;a href="https://eev.ee/blog/2018/10/09/cheezball-rising-opening-a-dialogue/"&gt;I draw some text to the screen&lt;/a&gt;.&lt;br /&gt;
Next: &lt;em&gt;more&lt;/em&gt; collision detection, and fixed-point&amp;nbsp;arithmetic.&lt;/p&gt;


&lt;h2 id="recap"&gt;&lt;a class="toclink" href="#recap"&gt;Recap&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Last time I avoided doing collision detection by writing a little dialogue system instead.  It was cute, and definitely something that needed doing, but something much more crucial still&amp;nbsp;looms.&lt;/p&gt;
&lt;div class="prose-full-illustration"&gt;
&lt;img src="https://eev.ee/media/cheezball/06k-quickbrownfox.gif" alt="Animation of the text box sliding up and scrolling out the text"&gt;
&lt;/div&gt;

&lt;p&gt;I&amp;#8217;ve put it off as long as I can.  If I want to get anywhere with actual gameplay, I&amp;#8217;m going to need some collision&amp;nbsp;detection.&lt;/p&gt;
&lt;h2 id="background-and-upfront-decisions"&gt;&lt;a class="toclink" href="#background-and-upfront-decisions"&gt;Background and upfront&amp;nbsp;decisions&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Collision detection is &lt;em&gt;hard&lt;/em&gt;.  It&amp;#8217;s a lot of math that happens a few pixels at a time.  Small mistakes can have dramatic consequences, yet be obscure enough that you don&amp;#8217;t even notice them.  Even using an off-the-shelf physics engine often requires dealing with a mountain of subtle quirks.  And did I mention I have to do it on a Game&amp;nbsp;Boy?&lt;/p&gt;
&lt;p&gt;Someday I&amp;#8217;ll write an article about everything I&amp;#8217;ve picked up about collision detection, but I haven&amp;#8217;t yet, so you get the quick version.  The problem is that an object is moving around, and it should be unable to move into solid objects.  There are two basic schools of thought about the&amp;nbsp;solution.&lt;/p&gt;
&lt;hr /&gt;
&lt;p&gt;&lt;strong&gt;Discrete&lt;/strong&gt; collision observes that an object moves in steps — a little chunk of movement every frame — and simply &lt;em&gt;teleports&lt;/em&gt; the object to its new location, then checks whether it now overlaps&amp;nbsp;anything.&lt;/p&gt;
&lt;div class="prose-full-illustration"&gt;
&lt;img src="https://eev.ee/media/cheezball/07a-discrete-collision.png" alt="Illustration of an object attempting to move into a wall"&gt;
&lt;/div&gt;

&lt;p&gt;(Note that all of these diagrams show very exaggerated motion.  In most games, objects are slow and frames are short, so nothing moves more than a pixel or two at a time.  That&amp;#8217;s another reason collision detection is hard: the steps are so small that it can be difficult to see what&amp;#8217;s actually going&amp;nbsp;on.)&lt;/p&gt;
&lt;p&gt;If it does overlap, you might might try to push it out of whatever it&amp;#8217;s overlapping, &lt;em&gt;or&lt;/em&gt; you might cancel the movement entirely and simply not move the object that&amp;nbsp;frame.&lt;/p&gt;
&lt;p&gt;Both approaches have drawbacks.  Pushing an object out of an obstacle isn&amp;#8217;t &lt;em&gt;too&lt;/em&gt; difficult a problem, but it&amp;#8217;s possible that the object will be pushed out &lt;em&gt;into&lt;/em&gt; another obstacle, and now you have a complicated problem.  (At this point, though, you could just give up and fall back to cancelling the&amp;nbsp;movement.)&lt;/p&gt;
&lt;p&gt;But cancelling the movement means that an object might get &amp;#8220;stuck&amp;#8221; a pixel or two away from a wall and never be able to butt up against it.  The faster the object is trying to move, the bigger the risk that this might&amp;nbsp;happen.&lt;/p&gt;
&lt;p&gt;That said, this is exactly how the original Doom engine handles collision, and it seems to work well enough there.  On the other hand, Doom is first-person so you can&amp;#8217;t easily tell if you&amp;#8217;re butting right up against a wall; a pixel gap is far more obvious in a game like this.  On the other other hand, Doom also has &lt;a href="https://doomwiki.org/wiki/Monsters_open_locked_doors"&gt;bugs&lt;/a&gt; where a fast monster can open a locked door &lt;em&gt;from its other side&lt;/em&gt;, because the initial teleport briefly moves the monster far enough into the door that it&amp;#8217;s touching the other (unlocked)&amp;nbsp;side.&lt;/p&gt;
&lt;p&gt;Sorry.  I have very conflicting feelings about this thicket of drawbacks and possible&amp;nbsp;workarounds.&lt;/p&gt;
&lt;p&gt;Either way, discrete collision has one other big drawback: &lt;em&gt;tunnelling&lt;/em&gt;.  Since the movement is done by teleporting, a very fast object might teleport right past a thin barrier.  Only the new position is checked for collisions, so the barrier is never noticed.  (This is how you travel to &lt;a href="https://www.youtube.com/watch?v=kpk2tdsPh0A"&gt;parallel universes in Mario 64&lt;/a&gt; — by building up enough speed that Mario teleports through walls without ever momentarily overlapping&amp;nbsp;them.)&lt;/p&gt;
&lt;div class="prose-full-illustration"&gt;
&lt;img src="https://eev.ee/media/cheezball/07b-discrete-collision-drawbacks.png" alt="Illustration of an object passing through a wall or erroneously pushing into one"&gt;
&lt;/div&gt;

&lt;p&gt;There are some other potential gotchas, though they&amp;#8217;re rare enough that I&amp;#8217;ve never seen anyone mention them.  One that stands out to me is that you don&amp;#8217;t know the &lt;em&gt;order&lt;/em&gt; that an object collided with obstacles, which might make a difference if the obstacles have special behavior when collided with and the order of that behavior&amp;nbsp;matters.&lt;/p&gt;
&lt;hr /&gt;
&lt;p&gt;&lt;strong&gt;Continuous&lt;/strong&gt; collision detection observes that game physics are trying to &lt;em&gt;simulate&lt;/em&gt; continuous motion, like happens in the real world, and tries to apply that to movement as well.  Instead of teleporting, objects &lt;em&gt;slide&lt;/em&gt; until they hit something.  Tunnelling is thus impossible, and there&amp;#8217;s no need to handle collisions since they&amp;#8217;re prevented in the first&amp;nbsp;place.&lt;/p&gt;
&lt;div class="prose-full-illustration"&gt;
&lt;img src="https://eev.ee/media/cheezball/07c-continuous-collision.png" alt="Illustration of an object sliding towards a wall and stopping when it touches"&gt;
&lt;/div&gt;

&lt;p&gt;This has some clear advantages, in that it eliminates all the pitfalls of discrete collision!  It even functions as a superset — if you want some object to act discretely, you could simply teleport it and then attempt to &amp;#8220;move&amp;#8221; it along the zero&amp;nbsp;vector.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;That said&lt;/em&gt;, continuous collision introduces some of its own problems.  The biggest (for my purposes, anyway) is that it&amp;#8217;s definitely more complicated to implement.  &amp;#8220;Sliding&amp;#8221; means figuring out which obstacle &lt;em&gt;would be hit first&lt;/em&gt;.  You can do raycasting in the direction of movement and see what the ray hits first, though that&amp;#8217;s imprecise and opens you up to new kinds of edge cases.  If you&amp;#8217;re lucky, you&amp;#8217;re using something like Unity and can &lt;a href="https://docs.unity3d.com/ScriptReference/Collider2D.Cast.html"&gt;cast the entire shape&lt;/a&gt; as a single unit.  Otherwise, well, you have to do a bunch of math to find everything in the swept path, then sort them in the order they&amp;#8217;d be&amp;nbsp;hit.&lt;/p&gt;
&lt;p&gt;The other big problem is that it&amp;#8217;s more work at &lt;em&gt;runtime&lt;/em&gt;.  With discrete collision, you only need to check for collisions in the new location.  That only costs more time when a lot of objects are bunched together in one place, which is unlikely.  With continuous collision, &lt;em&gt;everything&lt;/em&gt; along the swept path needs to be examined, and that means that &lt;em&gt;the faster an object moves, the more expensive its movement becomes&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;So, not quite a golden bullet for the tunnelling problem.  But that&amp;#8217;s not a surprise; the only way to prevent tunnelling is to check for objects between the start and end&amp;nbsp;positions.&lt;/p&gt;
&lt;hr /&gt;
&lt;p&gt;Which, then, do I want to implement&amp;nbsp;here?&lt;/p&gt;
&lt;p&gt;For platforms without floating point (including the &lt;span class="caps"&gt;PICO&lt;/span&gt;-8 and Game Boy), there&amp;#8217;s a third, hybrid option.  If everything&amp;#8217;s expressed with integers (or fixed point), then the universe has a Planck length: a minimum distance that every other distance must be an &lt;em&gt;integral&lt;/em&gt; multiple of.  You can thus &lt;em&gt;fake&lt;/em&gt; continuous collision by doing repeated steps of discrete collision, one Planck length at a time.  Objects will be collided with in the correct order, and you can simply stop at the first&amp;nbsp;overlap.&lt;/p&gt;
&lt;p&gt;Of course, this eats up a lot of time, since it involves doing collision detection numerous times per object per frame.  So unless your Planck length is really big, I&amp;#8217;m not sure it&amp;#8217;s worth&amp;nbsp;it.&lt;/p&gt;
&lt;p&gt;Instead, I&amp;#8217;m going to try for continuous collision.  It&amp;#8217;s closer to &amp;#8220;correct&amp;#8221; (whatever that means), and it&amp;#8217;s what I did for all of my other games so far.  It&amp;#8217;s definitely harder, thornier, more complicated, and slower, but dammit I like it.  It should also save me from encountering surprise bugs later on, which means I can write collision code once and then pretty much forget about it.&amp;nbsp;Ideal.&lt;/p&gt;
&lt;h2 id="getting-started"&gt;&lt;a class="toclink" href="#getting-started"&gt;Getting&amp;nbsp;started&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Star Anise is the only entity at the moment, so as a first pass, I&amp;#8217;m only going to implement collision with the &lt;em&gt;world&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;World collision is much easier!  Everything is laid out in a fixed grid, so I already know where the cells are.  Finding potential overlaps is fairly simple, and best of all, I don&amp;#8217;t need to sort anything to know what order the cells are&amp;nbsp;in.&lt;/p&gt;
&lt;p&gt;Right away, I find I have another decision to make.  I would normally want to use vector math here — the motion is some distance in some direction, and hey, that&amp;#8217;s a vector.  But vectors take up twice as much space (read: twice as many registers), and a &lt;em&gt;lot&lt;/em&gt; of vector operations rely on division or square roots which are non-trivial on this&amp;nbsp;hardware.&lt;/p&gt;
&lt;p&gt;With a great reluctant sigh, I thus commit to one more approximation, one made on 8-bit hardware since time immemorial.  I won&amp;#8217;t actually move in the direction of motion; instead, I&amp;#8217;ll move along the x-axis, then move along the y-axis &lt;em&gt;separately&lt;/em&gt;.  Diagonal movement could theoretically cut across some corners (or be unable to fit through very tight gaps), but those are very minor and unlikely inconveniences.  More importantly, this handwaving can&amp;#8217;t allow any &lt;em&gt;impossible&lt;/em&gt;&amp;nbsp;motion.&lt;/p&gt;
&lt;p&gt;I&amp;#8217;ve already taken for granted that entities will all be axis-aligned rectangles.  I&amp;#8217;m definitely not dealing with slopes on a goddamn Game Boy.  That was hard enough to do from scratch on a modern&amp;nbsp;computer.&lt;/p&gt;
&lt;p&gt;But I&amp;#8217;m getting ahead of myself.  First things first: you may recall that &lt;a href="https://eev.ee/blog/2018/07/05/cheezball-rising-main-loop-input-and-a-game/"&gt;Star Anise&amp;#8217;s movement&lt;/a&gt; is a bit of a hack.  Pressing a direction button only adds to or subtracts from the sprite coordinates in the &lt;span class="caps"&gt;OAM&lt;/span&gt; buffer; his position isn&amp;#8217;t actually stored in &lt;span class="caps"&gt;RAM&lt;/span&gt; anywhere.  In fact, thanks to my slightly nonlinear storytelling across these posts, his movement isn&amp;#8217;t stored anywhere either!  The input-reading code writes &lt;em&gt;directly&lt;/em&gt; to the &lt;span class="caps"&gt;OAM&lt;/span&gt; buffer.  Whoops.  I intended to fix that later, and now it&amp;#8217;s later, so here we&amp;nbsp;go.&lt;/p&gt;
&lt;table class="highlighttable"&gt;&lt;tr&gt;&lt;td class="linenos"&gt;&lt;div class="linenodiv"&gt;&lt;pre&gt;&lt;span class="normal"&gt;1&lt;/span&gt;
&lt;span class="normal"&gt;2&lt;/span&gt;
&lt;span class="normal"&gt;3&lt;/span&gt;
&lt;span class="normal"&gt;4&lt;/span&gt;
&lt;span class="normal"&gt;5&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/td&gt;&lt;td class="code"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="c1"&gt;; Somewhere in RAM, before anise_facing etc&lt;/span&gt;
&lt;span class="nf"&gt;anise_x:&lt;/span&gt;
    &lt;span class="k"&gt;db&lt;/span&gt;
&lt;span class="nf"&gt;anise_y:&lt;/span&gt;
    &lt;span class="k"&gt;db&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;p&gt;So far, so good.  &lt;span class="caps"&gt;OAM&lt;/span&gt; is populated in two places (and I should fix that later, too): once during setup, and once in the main game loop.  Both will need to be updated to use these&amp;nbsp;values.&lt;/p&gt;
&lt;p&gt;Setup needs to initialize them first, of&amp;nbsp;course:&lt;/p&gt;
&lt;table class="highlighttable"&gt;&lt;tr&gt;&lt;td class="linenos"&gt;&lt;div class="linenodiv"&gt;&lt;pre&gt;&lt;span class="normal"&gt;1&lt;/span&gt;
&lt;span class="normal"&gt;2&lt;/span&gt;
&lt;span class="normal"&gt;3&lt;/span&gt;
&lt;span class="normal"&gt;4&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/td&gt;&lt;td class="code"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;    &lt;span class="ow"&gt;ld&lt;/span&gt; &lt;span class="nb"&gt;a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;64&lt;/span&gt;
    &lt;span class="ow"&gt;ld&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;anise_x&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="nb"&gt;a&lt;/span&gt;
    &lt;span class="ow"&gt;ld&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;anise_y&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="nb"&gt;a&lt;/span&gt;
    &lt;span class="c1"&gt;; ... initialize anise_facing, etc ...&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;p&gt;And now the &lt;span class="caps"&gt;OAM&lt;/span&gt; setup can be fixed.  But, surprise!  I left myself another hardcoded knot to untangle: even the relative positions of the sprites are hardcoded.  Okay, so, &lt;em&gt;those&lt;/em&gt; need to be put somewhere too.  Eventually I&amp;#8217;m going to need some kinda entity structure, but since there&amp;#8217;s only one entity, I&amp;#8217;ll just slap it into a constant&amp;nbsp;somewhere.&lt;/p&gt;
&lt;p&gt;(I guess my programming philosophy is leaking out a bit here.  Don&amp;#8217;t worry about structure until you need it, and you don&amp;#8217;t need it until you need it twice.  Once code works for &lt;em&gt;one&lt;/em&gt; thing, it&amp;#8217;s relatively straightforward to make it work for &lt;em&gt;n&lt;/em&gt; things, and you have fewer things to worry about while you&amp;#8217;re just trying to make something&amp;nbsp;work.)&lt;/p&gt;
&lt;table class="highlighttable"&gt;&lt;tr&gt;&lt;td class="linenos"&gt;&lt;div class="linenodiv"&gt;&lt;pre&gt;&lt;span class="normal"&gt;1&lt;/span&gt;
&lt;span class="normal"&gt;2&lt;/span&gt;
&lt;span class="normal"&gt;3&lt;/span&gt;
&lt;span class="normal"&gt;4&lt;/span&gt;
&lt;span class="normal"&gt;5&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/td&gt;&lt;td class="code"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="c1"&gt;; In ROM somewhere&lt;/span&gt;
&lt;span class="nf"&gt;ANISE_SPRITE_POSITIONS:&lt;/span&gt;
    &lt;span class="k"&gt;db -2, -20&lt;/span&gt;
    &lt;span class="k"&gt;db -8, -14&lt;/span&gt;
    &lt;span class="k"&gt;db 0, -14&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;p&gt;It&amp;#8217;s not immediately obvious from looking at these numbers, but I&amp;#8217;m taking Star Anise&amp;#8217;s position to mean the point on the ground between his feet.  That&amp;#8217;s the best approximation of &lt;em&gt;where he is&lt;/em&gt;, after&amp;nbsp;all.&lt;/p&gt;
&lt;p&gt;(Early in game development, it seems natural to treat position as the upper-left corner of the sprite, so you can simply draw the sprite at the entity&amp;#8217;s position — but that tangles the world model up with the sprite you happen to have at the moment.  Imagine the havoc it&amp;#8217;d wreak if you changed the size of the sprite&amp;nbsp;later!)&lt;/p&gt;
&lt;p&gt;Okay, now I can&amp;nbsp;finally—&lt;/p&gt;
&lt;p&gt;What?  How does the code know there are exactly 3 sprites, on this byte-level platform?  Because I&amp;#8217;m hardcoding it.  Shut up already I&amp;#8217;ll &lt;em&gt;fix it&amp;nbsp;later&lt;/em&gt;&lt;/p&gt;
&lt;table class="highlighttable"&gt;&lt;tr&gt;&lt;td class="linenos"&gt;&lt;div class="linenodiv"&gt;&lt;pre&gt;&lt;span class="normal"&gt; 1&lt;/span&gt;
&lt;span class="normal"&gt; 2&lt;/span&gt;
&lt;span class="normal"&gt; 3&lt;/span&gt;
&lt;span class="normal"&gt; 4&lt;/span&gt;
&lt;span class="normal"&gt; 5&lt;/span&gt;
&lt;span class="normal"&gt; 6&lt;/span&gt;
&lt;span class="normal"&gt; 7&lt;/span&gt;
&lt;span class="normal"&gt; 8&lt;/span&gt;
&lt;span class="normal"&gt; 9&lt;/span&gt;
&lt;span class="normal"&gt;10&lt;/span&gt;
&lt;span class="normal"&gt;11&lt;/span&gt;
&lt;span class="normal"&gt;12&lt;/span&gt;
&lt;span class="normal"&gt;13&lt;/span&gt;
&lt;span class="normal"&gt;14&lt;/span&gt;
&lt;span class="normal"&gt;15&lt;/span&gt;
&lt;span class="normal"&gt;16&lt;/span&gt;
&lt;span class="normal"&gt;17&lt;/span&gt;
&lt;span class="normal"&gt;18&lt;/span&gt;
&lt;span class="normal"&gt;19&lt;/span&gt;
&lt;span class="normal"&gt;20&lt;/span&gt;
&lt;span class="normal"&gt;21&lt;/span&gt;
&lt;span class="normal"&gt;22&lt;/span&gt;
&lt;span class="normal"&gt;23&lt;/span&gt;
&lt;span class="normal"&gt;24&lt;/span&gt;
&lt;span class="normal"&gt;25&lt;/span&gt;
&lt;span class="normal"&gt;26&lt;/span&gt;
&lt;span class="normal"&gt;27&lt;/span&gt;
&lt;span class="normal"&gt;28&lt;/span&gt;
&lt;span class="normal"&gt;29&lt;/span&gt;
&lt;span class="normal"&gt;30&lt;/span&gt;
&lt;span class="normal"&gt;31&lt;/span&gt;
&lt;span class="normal"&gt;32&lt;/span&gt;
&lt;span class="normal"&gt;33&lt;/span&gt;
&lt;span class="normal"&gt;34&lt;/span&gt;
&lt;span class="normal"&gt;35&lt;/span&gt;
&lt;span class="normal"&gt;36&lt;/span&gt;
&lt;span class="normal"&gt;37&lt;/span&gt;
&lt;span class="normal"&gt;38&lt;/span&gt;
&lt;span class="normal"&gt;39&lt;/span&gt;
&lt;span class="normal"&gt;40&lt;/span&gt;
&lt;span class="normal"&gt;41&lt;/span&gt;
&lt;span class="normal"&gt;42&lt;/span&gt;
&lt;span class="normal"&gt;43&lt;/span&gt;
&lt;span class="normal"&gt;44&lt;/span&gt;
&lt;span class="normal"&gt;45&lt;/span&gt;
&lt;span class="normal"&gt;46&lt;/span&gt;
&lt;span class="normal"&gt;47&lt;/span&gt;
&lt;span class="normal"&gt;48&lt;/span&gt;
&lt;span class="normal"&gt;49&lt;/span&gt;
&lt;span class="normal"&gt;50&lt;/span&gt;
&lt;span class="normal"&gt;51&lt;/span&gt;
&lt;span class="normal"&gt;52&lt;/span&gt;
&lt;span class="normal"&gt;53&lt;/span&gt;
&lt;span class="normal"&gt;54&lt;/span&gt;
&lt;span class="normal"&gt;55&lt;/span&gt;
&lt;span class="normal"&gt;56&lt;/span&gt;
&lt;span class="normal"&gt;57&lt;/span&gt;
&lt;span class="normal"&gt;58&lt;/span&gt;
&lt;span class="normal"&gt;59&lt;/span&gt;
&lt;span class="normal"&gt;60&lt;/span&gt;
&lt;span class="normal"&gt;61&lt;/span&gt;
&lt;span class="normal"&gt;62&lt;/span&gt;
&lt;span class="normal"&gt;63&lt;/span&gt;
&lt;span class="normal"&gt;64&lt;/span&gt;
&lt;span class="normal"&gt;65&lt;/span&gt;
&lt;span class="normal"&gt;66&lt;/span&gt;
&lt;span class="normal"&gt;67&lt;/span&gt;
&lt;span class="normal"&gt;68&lt;/span&gt;
&lt;span class="normal"&gt;69&lt;/span&gt;
&lt;span class="normal"&gt;70&lt;/span&gt;
&lt;span class="normal"&gt;71&lt;/span&gt;
&lt;span class="normal"&gt;72&lt;/span&gt;
&lt;span class="normal"&gt;73&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/td&gt;&lt;td class="code"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;    &lt;span class="c1"&gt;; Load the x and y coordinates into the b and c registers&lt;/span&gt;
    &lt;span class="ow"&gt;ld&lt;/span&gt; &lt;span class="nb"&gt;hl&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;anise_x&lt;/span&gt;
    &lt;span class="ow"&gt;ld&lt;/span&gt; &lt;span class="nb"&gt;b&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;hl&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="ow"&gt;inc&lt;/span&gt; &lt;span class="nb"&gt;hl&lt;/span&gt;
    &lt;span class="ow"&gt;ld&lt;/span&gt; &lt;span class="nb"&gt;c&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;hl&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="c1"&gt;; Leave hl pointing at the sprite positions, which are&lt;/span&gt;
    &lt;span class="c1"&gt;; ordered so that hl+ will step through them correctly&lt;/span&gt;
    &lt;span class="ow"&gt;ld&lt;/span&gt; &lt;span class="nb"&gt;hl&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;ANISE_SPRITE_POSITIONS&lt;/span&gt;

    &lt;span class="c1"&gt;; ANTENNA&lt;/span&gt;
    &lt;span class="c1"&gt;; x-coord&lt;/span&gt;
    &lt;span class="c1"&gt;; The x coordinate needs to be added to the sprite offset,&lt;/span&gt;
    &lt;span class="c1"&gt;; AND the built-in OAM offset (8, 16).  Reading the sprite&lt;/span&gt;
    &lt;span class="c1"&gt;; offset first allows me to use hl+.&lt;/span&gt;
    &lt;span class="ow"&gt;ld&lt;/span&gt; &lt;span class="nb"&gt;a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;hl&lt;/span&gt;&lt;span class="p"&gt;+]&lt;/span&gt;
    &lt;span class="ow"&gt;add&lt;/span&gt; &lt;span class="nb"&gt;a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;b&lt;/span&gt;
    &lt;span class="ow"&gt;add&lt;/span&gt; &lt;span class="nb"&gt;a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;8&lt;/span&gt;
    &lt;span class="c1"&gt;; Previously, hl pointed into the OAM buffer and advanced&lt;/span&gt;
    &lt;span class="c1"&gt;; throughout this code, but now I&amp;#39;m using hl for something&lt;/span&gt;
    &lt;span class="c1"&gt;; else, so I use direct addresses of positions within the&lt;/span&gt;
    &lt;span class="c1"&gt;; buffer.  Obviously this is a kludge and won&amp;#39;t work once&lt;/span&gt;
    &lt;span class="c1"&gt;; I stop hardcoding sprites&amp;#39; positions in OAM, but, you&lt;/span&gt;
    &lt;span class="c1"&gt;; know, I&amp;#39;ll fix it later.&lt;/span&gt;
    &lt;span class="ow"&gt;ld&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;oam_buffer&lt;/span&gt; &lt;span class="p"&gt;+&lt;/span&gt; &lt;span class="nv"&gt;1&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="nb"&gt;a&lt;/span&gt;
    &lt;span class="c1"&gt;; y-coord&lt;/span&gt;
    &lt;span class="ow"&gt;ld&lt;/span&gt; &lt;span class="nb"&gt;a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;hl&lt;/span&gt;&lt;span class="p"&gt;+]&lt;/span&gt;
    &lt;span class="ow"&gt;add&lt;/span&gt; &lt;span class="nb"&gt;a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;c&lt;/span&gt;
    &lt;span class="ow"&gt;add&lt;/span&gt; &lt;span class="nb"&gt;a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;16&lt;/span&gt;
    &lt;span class="ow"&gt;ld&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;oam_buffer&lt;/span&gt; &lt;span class="p"&gt;+&lt;/span&gt; &lt;span class="nv"&gt;0&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="nb"&gt;a&lt;/span&gt;
    &lt;span class="c1"&gt;; This stuff is still hardcoded.&lt;/span&gt;
    &lt;span class="c1"&gt;; chr index&lt;/span&gt;
    &lt;span class="ow"&gt;xor&lt;/span&gt; &lt;span class="nb"&gt;a&lt;/span&gt;
    &lt;span class="ow"&gt;ld&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;oam_buffer&lt;/span&gt; &lt;span class="p"&gt;+&lt;/span&gt; &lt;span class="nv"&gt;2&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="nb"&gt;a&lt;/span&gt;
    &lt;span class="c1"&gt;; attributes&lt;/span&gt;
    &lt;span class="ow"&gt;ld&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;oam_buffer&lt;/span&gt; &lt;span class="p"&gt;+&lt;/span&gt; &lt;span class="nv"&gt;3&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="nb"&gt;a&lt;/span&gt;

    &lt;span class="c1"&gt;; The rest of this is not surprising.&lt;/span&gt;

    &lt;span class="c1"&gt;; LEFT PART&lt;/span&gt;
    &lt;span class="c1"&gt;; x-coord&lt;/span&gt;
    &lt;span class="ow"&gt;ld&lt;/span&gt; &lt;span class="nb"&gt;a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;hl&lt;/span&gt;&lt;span class="p"&gt;+]&lt;/span&gt;
    &lt;span class="ow"&gt;add&lt;/span&gt; &lt;span class="nb"&gt;a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;b&lt;/span&gt;
    &lt;span class="ow"&gt;add&lt;/span&gt; &lt;span class="nb"&gt;a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;8&lt;/span&gt;
    &lt;span class="ow"&gt;ld&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;oam_buffer&lt;/span&gt; &lt;span class="p"&gt;+&lt;/span&gt; &lt;span class="nv"&gt;5&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="nb"&gt;a&lt;/span&gt;
    &lt;span class="c1"&gt;; y-coord&lt;/span&gt;
    &lt;span class="ow"&gt;ld&lt;/span&gt; &lt;span class="nb"&gt;a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;hl&lt;/span&gt;&lt;span class="p"&gt;+]&lt;/span&gt;
    &lt;span class="ow"&gt;add&lt;/span&gt; &lt;span class="nb"&gt;a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;c&lt;/span&gt;
    &lt;span class="ow"&gt;add&lt;/span&gt; &lt;span class="nb"&gt;a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;16&lt;/span&gt;
    &lt;span class="ow"&gt;ld&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;oam_buffer&lt;/span&gt; &lt;span class="p"&gt;+&lt;/span&gt; &lt;span class="nv"&gt;4&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="nb"&gt;a&lt;/span&gt;
    &lt;span class="c1"&gt;; chr index&lt;/span&gt;
    &lt;span class="ow"&gt;ld&lt;/span&gt; &lt;span class="nb"&gt;a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;2&lt;/span&gt;
    &lt;span class="ow"&gt;ld&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;oam_buffer&lt;/span&gt; &lt;span class="p"&gt;+&lt;/span&gt; &lt;span class="nv"&gt;6&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="nb"&gt;a&lt;/span&gt;
    &lt;span class="c1"&gt;; attributes&lt;/span&gt;
    &lt;span class="ow"&gt;ld&lt;/span&gt; &lt;span class="nb"&gt;a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mb"&gt;%00000001&lt;/span&gt;
    &lt;span class="ow"&gt;ld&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;oam_buffer&lt;/span&gt; &lt;span class="p"&gt;+&lt;/span&gt; &lt;span class="nv"&gt;7&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="nb"&gt;a&lt;/span&gt;

    &lt;span class="c1"&gt;; RIGHT PART&lt;/span&gt;
    &lt;span class="c1"&gt;; x-coord&lt;/span&gt;
    &lt;span class="ow"&gt;ld&lt;/span&gt; &lt;span class="nb"&gt;a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;hl&lt;/span&gt;&lt;span class="p"&gt;+]&lt;/span&gt;
    &lt;span class="ow"&gt;add&lt;/span&gt; &lt;span class="nb"&gt;a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;b&lt;/span&gt;
    &lt;span class="ow"&gt;add&lt;/span&gt; &lt;span class="nb"&gt;a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;8&lt;/span&gt;
    &lt;span class="ow"&gt;ld&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;oam_buffer&lt;/span&gt; &lt;span class="p"&gt;+&lt;/span&gt; &lt;span class="nv"&gt;9&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="nb"&gt;a&lt;/span&gt;
    &lt;span class="c1"&gt;; y-coord&lt;/span&gt;
    &lt;span class="ow"&gt;ld&lt;/span&gt; &lt;span class="nb"&gt;a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;hl&lt;/span&gt;&lt;span class="p"&gt;+]&lt;/span&gt;
    &lt;span class="ow"&gt;add&lt;/span&gt; &lt;span class="nb"&gt;a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;c&lt;/span&gt;
    &lt;span class="ow"&gt;add&lt;/span&gt; &lt;span class="nb"&gt;a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;16&lt;/span&gt;
    &lt;span class="ow"&gt;ld&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;oam_buffer&lt;/span&gt; &lt;span class="p"&gt;+&lt;/span&gt; &lt;span class="nv"&gt;8&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="nb"&gt;a&lt;/span&gt;
    &lt;span class="c1"&gt;; chr index&lt;/span&gt;
    &lt;span class="ow"&gt;ld&lt;/span&gt; &lt;span class="nb"&gt;a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;4&lt;/span&gt;
    &lt;span class="ow"&gt;ld&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;oam_buffer&lt;/span&gt; &lt;span class="p"&gt;+&lt;/span&gt; &lt;span class="nv"&gt;10&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="nb"&gt;a&lt;/span&gt;
    &lt;span class="c1"&gt;; attributes&lt;/span&gt;
    &lt;span class="ow"&gt;ld&lt;/span&gt; &lt;span class="nb"&gt;a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mb"&gt;%00000001&lt;/span&gt;
    &lt;span class="ow"&gt;ld&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;oam_buffer&lt;/span&gt; &lt;span class="p"&gt;+&lt;/span&gt; &lt;span class="nv"&gt;11&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="nb"&gt;a&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;p&gt;Boot up the game, and…  it looks the same!  That&amp;#8217;s going to be a running theme for a little bit here.  Sorry, this isn&amp;#8217;t a particularly screenshot-heavy post.  It&amp;#8217;s all gonna be math and code for a&amp;nbsp;while.&lt;/p&gt;
&lt;p&gt;Now I need to split apart the code that reads input and applies movement to &lt;span class="caps"&gt;OAM&lt;/span&gt;.  Reading input gets much simpler, since it doesn&amp;#8217;t have to do anything any more, just compute a dx and&amp;nbsp;dy.&lt;/p&gt;
&lt;p&gt;This code does still have looming questions, such as how to handle pressing two opposite directions (which is impossible on hardware but easy on an emulator), or whether diagonal movement should be fixed so that Anise doesn&amp;#8217;t move at &lt;span class="math"&gt;\(\sqrt{2}\)&lt;/span&gt; his movement&amp;nbsp;speed.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Later.&lt;/em&gt;  Seriously the actual code has so many &lt;code&gt;XXX&lt;/code&gt; and &lt;code&gt;TODO&lt;/code&gt; and &lt;code&gt;FIXME&lt;/code&gt; comments that I edit out of these&amp;nbsp;posts.&lt;/p&gt;
&lt;table class="highlighttable"&gt;&lt;tr&gt;&lt;td class="linenos"&gt;&lt;div class="linenodiv"&gt;&lt;pre&gt;&lt;span class="normal"&gt; 1&lt;/span&gt;
&lt;span class="normal"&gt; 2&lt;/span&gt;
&lt;span class="normal"&gt; 3&lt;/span&gt;
&lt;span class="normal"&gt; 4&lt;/span&gt;
&lt;span class="normal"&gt; 5&lt;/span&gt;
&lt;span class="normal"&gt; 6&lt;/span&gt;
&lt;span class="normal"&gt; 7&lt;/span&gt;
&lt;span class="normal"&gt; 8&lt;/span&gt;
&lt;span class="normal"&gt; 9&lt;/span&gt;
&lt;span class="normal"&gt;10&lt;/span&gt;
&lt;span class="normal"&gt;11&lt;/span&gt;
&lt;span class="normal"&gt;12&lt;/span&gt;
&lt;span class="normal"&gt;13&lt;/span&gt;
&lt;span class="normal"&gt;14&lt;/span&gt;
&lt;span class="normal"&gt;15&lt;/span&gt;
&lt;span class="normal"&gt;16&lt;/span&gt;
&lt;span class="normal"&gt;17&lt;/span&gt;
&lt;span class="normal"&gt;18&lt;/span&gt;
&lt;span class="normal"&gt;19&lt;/span&gt;
&lt;span class="normal"&gt;20&lt;/span&gt;
&lt;span class="normal"&gt;21&lt;/span&gt;
&lt;span class="normal"&gt;22&lt;/span&gt;
&lt;span class="normal"&gt;23&lt;/span&gt;
&lt;span class="normal"&gt;24&lt;/span&gt;
&lt;span class="normal"&gt;25&lt;/span&gt;
&lt;span class="normal"&gt;26&lt;/span&gt;
&lt;span class="normal"&gt;27&lt;/span&gt;
&lt;span class="normal"&gt;28&lt;/span&gt;
&lt;span class="normal"&gt;29&lt;/span&gt;
&lt;span class="normal"&gt;30&lt;/span&gt;
&lt;span class="normal"&gt;31&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/td&gt;&lt;td class="code"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;    &lt;span class="c1"&gt;; Anise update loop&lt;/span&gt;
    &lt;span class="c1"&gt;; Stick dx and dy in the b and c registers.&lt;/span&gt;
    &lt;span class="ow"&gt;ld&lt;/span&gt; &lt;span class="nb"&gt;a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;buttons&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="c1"&gt;; b/c: dx/dy&lt;/span&gt;
    &lt;span class="ow"&gt;ld&lt;/span&gt; &lt;span class="nb"&gt;b&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;0&lt;/span&gt;
    &lt;span class="ow"&gt;ld&lt;/span&gt; &lt;span class="nb"&gt;c&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;0&lt;/span&gt;
    &lt;span class="ow"&gt;bit&lt;/span&gt; &lt;span class="nv"&gt;PADB_LEFT&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;a&lt;/span&gt;
    &lt;span class="ow"&gt;jr&lt;/span&gt; &lt;span class="nv"&gt;z&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="err"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;skip_left&lt;/span&gt;
    &lt;span class="ow"&gt;dec&lt;/span&gt; &lt;span class="nb"&gt;b&lt;/span&gt;
&lt;span class="nf"&gt;.skip_left:&lt;/span&gt;
    &lt;span class="ow"&gt;bit&lt;/span&gt; &lt;span class="nv"&gt;PADB_RIGHT&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;a&lt;/span&gt;
    &lt;span class="ow"&gt;jr&lt;/span&gt; &lt;span class="nv"&gt;z&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="err"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;skip_right&lt;/span&gt;
    &lt;span class="ow"&gt;inc&lt;/span&gt; &lt;span class="nb"&gt;b&lt;/span&gt;
&lt;span class="nf"&gt;.skip_right:&lt;/span&gt;
    &lt;span class="ow"&gt;bit&lt;/span&gt; &lt;span class="nv"&gt;PADB_UP&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;a&lt;/span&gt;
    &lt;span class="ow"&gt;jr&lt;/span&gt; &lt;span class="nv"&gt;z&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="err"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;skip_up&lt;/span&gt;
    &lt;span class="ow"&gt;dec&lt;/span&gt; &lt;span class="nb"&gt;c&lt;/span&gt;
&lt;span class="nf"&gt;.skip_up:&lt;/span&gt;
    &lt;span class="ow"&gt;bit&lt;/span&gt; &lt;span class="nv"&gt;PADB_DOWN&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;a&lt;/span&gt;
    &lt;span class="ow"&gt;jr&lt;/span&gt; &lt;span class="nv"&gt;z&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="err"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;skip_down&lt;/span&gt;
    &lt;span class="ow"&gt;inc&lt;/span&gt; &lt;span class="nb"&gt;c&lt;/span&gt;
&lt;span class="nf"&gt;.skip_down:&lt;/span&gt;

    &lt;span class="c1"&gt;; For now just add b and c to Anise&amp;#39;s coordinates.  This&lt;/span&gt;
    &lt;span class="c1"&gt;; is where collision detection will go in a moment!&lt;/span&gt;
    &lt;span class="ow"&gt;ld&lt;/span&gt; &lt;span class="nb"&gt;a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;anise_x&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="ow"&gt;add&lt;/span&gt; &lt;span class="nb"&gt;a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;b&lt;/span&gt;
    &lt;span class="ow"&gt;ld&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;anise_x&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="nb"&gt;a&lt;/span&gt;
    &lt;span class="ow"&gt;ld&lt;/span&gt; &lt;span class="nb"&gt;a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;anise_y&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="ow"&gt;add&lt;/span&gt; &lt;span class="nb"&gt;a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;c&lt;/span&gt;
    &lt;span class="ow"&gt;ld&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;anise_y&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="nb"&gt;c&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;p&gt;All that&amp;#8217;s left is to more explicitly update the &lt;span class="caps"&gt;OAM&lt;/span&gt;&amp;nbsp;buffer!&lt;/p&gt;
&lt;p&gt;This code ends up looking fairly similar to the setup code.  So similar, in fact, that I wonder if these blocks should be merged, but I&amp;#8217;ll do that&amp;nbsp;later:&lt;/p&gt;
&lt;table class="highlighttable"&gt;&lt;tr&gt;&lt;td class="linenos"&gt;&lt;div class="linenodiv"&gt;&lt;pre&gt;&lt;span class="normal"&gt; 1&lt;/span&gt;
&lt;span class="normal"&gt; 2&lt;/span&gt;
&lt;span class="normal"&gt; 3&lt;/span&gt;
&lt;span class="normal"&gt; 4&lt;/span&gt;
&lt;span class="normal"&gt; 5&lt;/span&gt;
&lt;span class="normal"&gt; 6&lt;/span&gt;
&lt;span class="normal"&gt; 7&lt;/span&gt;
&lt;span class="normal"&gt; 8&lt;/span&gt;
&lt;span class="normal"&gt; 9&lt;/span&gt;
&lt;span class="normal"&gt;10&lt;/span&gt;
&lt;span class="normal"&gt;11&lt;/span&gt;
&lt;span class="normal"&gt;12&lt;/span&gt;
&lt;span class="normal"&gt;13&lt;/span&gt;
&lt;span class="normal"&gt;14&lt;/span&gt;
&lt;span class="normal"&gt;15&lt;/span&gt;
&lt;span class="normal"&gt;16&lt;/span&gt;
&lt;span class="normal"&gt;17&lt;/span&gt;
&lt;span class="normal"&gt;18&lt;/span&gt;
&lt;span class="normal"&gt;19&lt;/span&gt;
&lt;span class="normal"&gt;20&lt;/span&gt;
&lt;span class="normal"&gt;21&lt;/span&gt;
&lt;span class="normal"&gt;22&lt;/span&gt;
&lt;span class="normal"&gt;23&lt;/span&gt;
&lt;span class="normal"&gt;24&lt;/span&gt;
&lt;span class="normal"&gt;25&lt;/span&gt;
&lt;span class="normal"&gt;26&lt;/span&gt;
&lt;span class="normal"&gt;27&lt;/span&gt;
&lt;span class="normal"&gt;28&lt;/span&gt;
&lt;span class="normal"&gt;29&lt;/span&gt;
&lt;span class="normal"&gt;30&lt;/span&gt;
&lt;span class="normal"&gt;31&lt;/span&gt;
&lt;span class="normal"&gt;32&lt;/span&gt;
&lt;span class="normal"&gt;33&lt;/span&gt;
&lt;span class="normal"&gt;34&lt;/span&gt;
&lt;span class="normal"&gt;35&lt;/span&gt;
&lt;span class="normal"&gt;36&lt;/span&gt;
&lt;span class="normal"&gt;37&lt;/span&gt;
&lt;span class="normal"&gt;38&lt;/span&gt;
&lt;span class="normal"&gt;39&lt;/span&gt;
&lt;span class="normal"&gt;40&lt;/span&gt;
&lt;span class="normal"&gt;41&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/td&gt;&lt;td class="code"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;    &lt;span class="c1"&gt;; Load x and y into b and c&lt;/span&gt;
    &lt;span class="ow"&gt;ld&lt;/span&gt; &lt;span class="nb"&gt;hl&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;anise_x&lt;/span&gt;
    &lt;span class="ow"&gt;ld&lt;/span&gt; &lt;span class="nb"&gt;b&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;hl&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="ow"&gt;inc&lt;/span&gt; &lt;span class="nb"&gt;hl&lt;/span&gt;
    &lt;span class="ow"&gt;ld&lt;/span&gt; &lt;span class="nb"&gt;c&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;hl&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="c1"&gt;; Point hl at the sprite positions&lt;/span&gt;
    &lt;span class="ow"&gt;ld&lt;/span&gt; &lt;span class="nb"&gt;hl&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;ANISE_SPRITE_POSITIONS&lt;/span&gt;

    &lt;span class="c1"&gt;; ANTENNA&lt;/span&gt;
    &lt;span class="c1"&gt;; x-coord&lt;/span&gt;
    &lt;span class="ow"&gt;ld&lt;/span&gt; &lt;span class="nb"&gt;a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;hl&lt;/span&gt;&lt;span class="p"&gt;+]&lt;/span&gt;
    &lt;span class="ow"&gt;add&lt;/span&gt; &lt;span class="nb"&gt;a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;b&lt;/span&gt;
    &lt;span class="ow"&gt;add&lt;/span&gt; &lt;span class="nb"&gt;a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;8&lt;/span&gt;
    &lt;span class="ow"&gt;ld&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;oam_buffer&lt;/span&gt; &lt;span class="p"&gt;+&lt;/span&gt; &lt;span class="nv"&gt;1&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="nb"&gt;a&lt;/span&gt;
    &lt;span class="c1"&gt;; y-coord&lt;/span&gt;
    &lt;span class="ow"&gt;ld&lt;/span&gt; &lt;span class="nb"&gt;a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;hl&lt;/span&gt;&lt;span class="p"&gt;+]&lt;/span&gt;
    &lt;span class="ow"&gt;add&lt;/span&gt; &lt;span class="nb"&gt;a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;c&lt;/span&gt;
    &lt;span class="ow"&gt;add&lt;/span&gt; &lt;span class="nb"&gt;a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;16&lt;/span&gt;
    &lt;span class="ow"&gt;ld&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;oam_buffer&lt;/span&gt; &lt;span class="p"&gt;+&lt;/span&gt; &lt;span class="nv"&gt;0&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="nb"&gt;a&lt;/span&gt;
    &lt;span class="c1"&gt;; LEFT PART&lt;/span&gt;
    &lt;span class="c1"&gt;; x-coord&lt;/span&gt;
    &lt;span class="ow"&gt;ld&lt;/span&gt; &lt;span class="nb"&gt;a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;hl&lt;/span&gt;&lt;span class="p"&gt;+]&lt;/span&gt;
    &lt;span class="ow"&gt;add&lt;/span&gt; &lt;span class="nb"&gt;a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;b&lt;/span&gt;
    &lt;span class="ow"&gt;add&lt;/span&gt; &lt;span class="nb"&gt;a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;8&lt;/span&gt;
    &lt;span class="ow"&gt;ld&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;oam_buffer&lt;/span&gt; &lt;span class="p"&gt;+&lt;/span&gt; &lt;span class="nv"&gt;5&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="nb"&gt;a&lt;/span&gt;
    &lt;span class="c1"&gt;; y-coord&lt;/span&gt;
    &lt;span class="ow"&gt;ld&lt;/span&gt; &lt;span class="nb"&gt;a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;hl&lt;/span&gt;&lt;span class="p"&gt;+]&lt;/span&gt;
    &lt;span class="ow"&gt;add&lt;/span&gt; &lt;span class="nb"&gt;a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;c&lt;/span&gt;
    &lt;span class="ow"&gt;add&lt;/span&gt; &lt;span class="nb"&gt;a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;16&lt;/span&gt;
    &lt;span class="ow"&gt;ld&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;oam_buffer&lt;/span&gt; &lt;span class="p"&gt;+&lt;/span&gt; &lt;span class="nv"&gt;4&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="nb"&gt;a&lt;/span&gt;
    &lt;span class="c1"&gt;; RIGHT PART&lt;/span&gt;
    &lt;span class="c1"&gt;; x-coord&lt;/span&gt;
    &lt;span class="ow"&gt;ld&lt;/span&gt; &lt;span class="nb"&gt;a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;hl&lt;/span&gt;&lt;span class="p"&gt;+]&lt;/span&gt;
    &lt;span class="ow"&gt;add&lt;/span&gt; &lt;span class="nb"&gt;a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;b&lt;/span&gt;
    &lt;span class="ow"&gt;add&lt;/span&gt; &lt;span class="nb"&gt;a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;8&lt;/span&gt;
    &lt;span class="ow"&gt;ld&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;oam_buffer&lt;/span&gt; &lt;span class="p"&gt;+&lt;/span&gt; &lt;span class="nv"&gt;9&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="nb"&gt;a&lt;/span&gt;
    &lt;span class="c1"&gt;; y-coord&lt;/span&gt;
    &lt;span class="ow"&gt;ld&lt;/span&gt; &lt;span class="nb"&gt;a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;hl&lt;/span&gt;&lt;span class="p"&gt;+]&lt;/span&gt;
    &lt;span class="ow"&gt;add&lt;/span&gt; &lt;span class="nb"&gt;a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;c&lt;/span&gt;
    &lt;span class="ow"&gt;add&lt;/span&gt; &lt;span class="nb"&gt;a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;16&lt;/span&gt;
    &lt;span class="ow"&gt;ld&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;oam_buffer&lt;/span&gt; &lt;span class="p"&gt;+&lt;/span&gt; &lt;span class="nv"&gt;8&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="nb"&gt;a&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;p&gt;Phew!  And the game plays exactly the same as before.  Programming is so&amp;nbsp;rewarding.&lt;/p&gt;
&lt;p&gt;On to the main&amp;nbsp;course!&lt;/p&gt;
&lt;h2 id="collision-detection-sort-of"&gt;&lt;a class="toclink" href="#collision-detection-sort-of"&gt;Collision detection, sort&amp;nbsp;of&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;So.  First pass.  Star Anise can only collide with the&amp;nbsp;map.&lt;/p&gt;
&lt;p&gt;Ah, but first, what size is Star Anise himself?  I&amp;#8217;ve only given him a position, not a hitbox.  I could use his sprite as the hitbox, but with his helmet being much bigger than his body, that&amp;#8217;ll make it seem like he can&amp;#8217;t get closer than a foot to anything else.  I&amp;#8217;d prefer if he had an explicit&amp;nbsp;radius.&lt;/p&gt;
&lt;table class="highlighttable"&gt;&lt;tr&gt;&lt;td class="linenos"&gt;&lt;div class="linenodiv"&gt;&lt;pre&gt;&lt;span class="normal"&gt;1&lt;/span&gt;
&lt;span class="normal"&gt;2&lt;/span&gt;
&lt;span class="normal"&gt;3&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/td&gt;&lt;td class="code"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="c1"&gt;; in ROM somewhere&lt;/span&gt;
&lt;span class="nf"&gt;ANISE_RADIUS:&lt;/span&gt;
    &lt;span class="k"&gt;db 3&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;p&gt;Remember, Star Anise&amp;#8217;s position is the point between his feet.  This describes his hitbox as a square, centered at that point, with sides 6 pixels long.  The top and bottom edges of his hitbox are thus at &lt;code&gt;y - r&lt;/code&gt; and &lt;code&gt;y + r&lt;/code&gt;, which makes for some pleasing&amp;nbsp;symmetry.&lt;/p&gt;
&lt;p&gt;(Making hitboxes square doesn&amp;#8217;t save a lot of effort or anything, but switching to rectangles later on wouldn&amp;#8217;t be especially difficult&amp;nbsp;either.)&lt;/p&gt;
&lt;h3 id="the-plan"&gt;&lt;a class="toclink" href="#the-plan"&gt;The&amp;nbsp;plan&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;My plan for moving rightwards, which I came up with after a lot of very careful and very messy sketching, looks like&amp;nbsp;this:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;Figure out which rows I&amp;#8217;m&amp;nbsp;spanning.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Move right until the next grid line.  No new obstacle can possibly be encountered until then, so there&amp;#8217;s nothing to&amp;nbsp;check.&lt;/p&gt;
&lt;p&gt;(Unless I&amp;#8217;m somehow already overlapping an obstacle, of course, but then I&amp;#8217;d rather be able to move &lt;em&gt;out&lt;/em&gt; of the obstacle than stay stuck and possibly softlock the&amp;nbsp;game.)&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;In the next grid column, check every cell that&amp;#8217;s in a spanned row.  If &lt;em&gt;any&lt;/em&gt; of those cells block us, &lt;strong&gt;stop here&lt;/strong&gt;.  Otherwise, move to the next grid line (8&amp;nbsp;pixels).&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Repeat until I run out of&amp;nbsp;movement.&lt;/p&gt;
&lt;p&gt;(It&amp;#8217;s very unlikely the previous step would happen more than once; an entity would have to move more than 8 pixels per frame, which is 3 entire screen widths per&amp;nbsp;second.)&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Here&amp;#8217;s a diagram.  In this case, step 3 checks two cells for each column, but it might check more or fewer depending on how the entity is positioned.  (It&amp;#8217;ll never need to check more than one cell more than the entity&amp;#8217;s&amp;nbsp;height.)&lt;/p&gt;
&lt;div class="prose-full-illustration"&gt;
&lt;img src="https://eev.ee/media/cheezball/07d-algorithm-overview.png" alt="Illustration of the above algorithm"&gt;
&lt;/div&gt;

&lt;p&gt;Seems straightforward enough.  But&amp;nbsp;wait!&lt;/p&gt;
&lt;h3 id="edge-case"&gt;&lt;a class="toclink" href="#edge-case"&gt;Edge&amp;nbsp;case&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;I&amp;#8217;ll save you a bunch of debugging anguish on my part and skip to the punchline: there&amp;#8217;s an edge&amp;nbsp;case.&lt;/p&gt;
&lt;p&gt;I mean, literally, the case of when the entity&amp;#8217;s edge is already against a grid line.  That&amp;#8217;ll happen fairly frequently — every time an entity collides with the map, it&amp;#8217;ll naturally stop with its edge aligned to the&amp;nbsp;grid.&lt;/p&gt;
&lt;p&gt;The problem is all the way back in step 1.  Remember, I said that to figure out which grid row or column a point belongs to, I need to divide by 8 (or shift right by 3).  So the rows an entity spans must count from its top edge divided by 8, to its bottom edge divided by 8.&amp;nbsp;Right?&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Well…&lt;/em&gt;&lt;/p&gt;
&lt;div class="prose-full-illustration"&gt;
&lt;img src="https://eev.ee/media/cheezball/07e-edge-case.png" alt="Diagram showing division by 8 for several possible positions; when the bottom of the entity touches a grid line, it appears to be jutting into the row below"&gt;
&lt;/div&gt;

&lt;p&gt;Everything&amp;#8217;s fine until the entity&amp;#8217;s bottom edge is exactly flush with the grid line, as in the last example.  Then it seems to be jutting into the row below, even though no part of it is actually inside that row.  If the entity tried to move rightwards from here, it might get blocked on something in row 1!  Even worse, if row 1 were a solid wall that it had just run into, it wouldn&amp;#8217;t be able to move left or right at&amp;nbsp;all!&lt;/p&gt;
&lt;p&gt;What happened here?  There&amp;#8217;s a hint in how I laid out the&amp;nbsp;diagram.&lt;/p&gt;
&lt;p&gt;There&amp;#8217;s something akin to the fencepost problem here.  I&amp;#8217;ve been talking about rows and columns of the grid as if they were &lt;em&gt;regions&lt;/em&gt; — &amp;#8220;row 1&amp;#8221; labels a rectangular strip of the world.  But pixel coordinates don&amp;#8217;t describe regions!  They describe &lt;em&gt;points&lt;/em&gt;.  A pixel is a square area, but a pixel coordinate is the point at the &lt;em&gt;upper left corner&lt;/em&gt; of that&amp;nbsp;area.&lt;/p&gt;
&lt;p&gt;In the incorrect example, the bottom of the entity is at y = 8, even though the row of &lt;em&gt;pixels&lt;/em&gt; described by y = 8 doesn&amp;#8217;t contain any part of the hitbox.  I&amp;#8217;m using the coordinate of the pixel&amp;#8217;s &lt;em&gt;top&lt;/em&gt; edge to describe a box&amp;#8217;s &lt;em&gt;bottom&lt;/em&gt; edge, and it falls apart when I try to reinterpret that coordinate as a region.  In terms of area, y = 8 really names the first row of pixels that the entity &lt;em&gt;doesn&amp;#8217;t&lt;/em&gt;&amp;nbsp;overlap.&lt;/p&gt;
&lt;p&gt;To work around this, I need to adjust how I convert a coordinate to the corresponding grid cell, but &lt;strong&gt;only&lt;/strong&gt; when that coordinate describes the right or bottom of a bounding box.  Bottom pixel 8 should belong to row 0, but 9 should still end up in row&amp;nbsp;1.&lt;/p&gt;
&lt;p&gt;As luck would have it, I&amp;#8217;m using integers for coordinates, which means there&amp;#8217;s a &lt;em&gt;Planck length&lt;/em&gt; — a minimum distance of which all other distances are a multiple.  That length is, of course, 1 pixel.  If I subtract that length from a bottom coordinate, I get the next nearest coordinate going upwards.  If the original coordinate was on a grid line, it&amp;#8217;ll retreat back into the cell above; otherwise, it&amp;#8217;ll stay in the same cell.  You can check this with the diagram, if you need some&amp;nbsp;convincing.&lt;/p&gt;
&lt;p&gt;(This works for any &lt;a href="https://en.wikipedia.org/wiki/Fixed-point_arithmetic"&gt;fixed point&lt;/a&gt; system; integers are the special case of fixed point with zero fractional bits.  It would &lt;strong&gt;not&lt;/strong&gt; work so easily with floating point — subtracting the smallest possible float value will usually do nothing, because there&amp;#8217;s not enough precision to express the difference.  But then, if you have floating point, you probably have division and can write vector-based collision instead of taking grid-based&amp;nbsp;shortcuts.)&lt;/p&gt;
&lt;p&gt;All that is to say, I just need to subtract 1 before shifting.  For clarity, I&amp;#8217;ll write these as macros to convert a coordinate in &lt;code&gt;a&lt;/code&gt; to a grid cell.  I call the top or left conversion &lt;em&gt;inclusive&lt;/em&gt;, because it includes the pixel the coordinate refers to; conversely, the bottom and right conversion is &lt;em&gt;exclusive&lt;/em&gt;, like how a bottom of 8 actually excludes the pixels at y =&amp;nbsp;8.&lt;/p&gt;
&lt;table class="highlighttable"&gt;&lt;tr&gt;&lt;td class="linenos"&gt;&lt;div class="linenodiv"&gt;&lt;pre&gt;&lt;span class="normal"&gt; 1&lt;/span&gt;
&lt;span class="normal"&gt; 2&lt;/span&gt;
&lt;span class="normal"&gt; 3&lt;/span&gt;
&lt;span class="normal"&gt; 4&lt;/span&gt;
&lt;span class="normal"&gt; 5&lt;/span&gt;
&lt;span class="normal"&gt; 6&lt;/span&gt;
&lt;span class="normal"&gt; 7&lt;/span&gt;
&lt;span class="normal"&gt; 8&lt;/span&gt;
&lt;span class="normal"&gt; 9&lt;/span&gt;
&lt;span class="normal"&gt;10&lt;/span&gt;
&lt;span class="normal"&gt;11&lt;/span&gt;
&lt;span class="normal"&gt;12&lt;/span&gt;
&lt;span class="normal"&gt;13&lt;/span&gt;
&lt;span class="normal"&gt;14&lt;/span&gt;
&lt;span class="normal"&gt;15&lt;/span&gt;
&lt;span class="normal"&gt;16&lt;/span&gt;
&lt;span class="normal"&gt;17&lt;/span&gt;
&lt;span class="normal"&gt;18&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/td&gt;&lt;td class="code"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="c1"&gt;; Given a point on the top or left of a box, convert it to the&lt;/span&gt;
&lt;span class="c1"&gt;; containing grid cell.&lt;/span&gt;
&lt;span class="nf"&gt;ToInclusiveCell:&lt;/span&gt; &lt;span class="k"&gt;MACRO&lt;/span&gt;
    &lt;span class="c1"&gt;; This is just floor division&lt;/span&gt;
    &lt;span class="ow"&gt;srl&lt;/span&gt; &lt;span class="nb"&gt;a&lt;/span&gt;
    &lt;span class="ow"&gt;srl&lt;/span&gt; &lt;span class="nb"&gt;a&lt;/span&gt;
    &lt;span class="ow"&gt;srl&lt;/span&gt; &lt;span class="nb"&gt;a&lt;/span&gt;
&lt;span class="k"&gt;ENDM&lt;/span&gt;
&lt;span class="c1"&gt;; Given a point on the bottom or right of a box, convert it to&lt;/span&gt;
&lt;span class="c1"&gt;; the containing grid cell.&lt;/span&gt;
&lt;span class="nf"&gt;ToExclusiveCell:&lt;/span&gt; &lt;span class="k"&gt;MACRO&lt;/span&gt;
    &lt;span class="c1"&gt;; Deal with the exclusive edge by subtracting the planck&lt;/span&gt;
    &lt;span class="c1"&gt;; length, then flooring&lt;/span&gt;
    &lt;span class="ow"&gt;dec&lt;/span&gt; &lt;span class="nb"&gt;a&lt;/span&gt;
    &lt;span class="ow"&gt;srl&lt;/span&gt; &lt;span class="nb"&gt;a&lt;/span&gt;
    &lt;span class="ow"&gt;srl&lt;/span&gt; &lt;span class="nb"&gt;a&lt;/span&gt;
    &lt;span class="ow"&gt;srl&lt;/span&gt; &lt;span class="nb"&gt;a&lt;/span&gt;
&lt;span class="k"&gt;ENDM&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;p&gt;At last, I can write some damn&amp;nbsp;code!&lt;/p&gt;
&lt;h3 id="some-damn-code"&gt;&lt;a class="toclink" href="#some-damn-code"&gt;Some damn&amp;nbsp;code&lt;/a&gt;&lt;/h3&gt;
&lt;table class="highlighttable"&gt;&lt;tr&gt;&lt;td class="linenos"&gt;&lt;div class="linenodiv"&gt;&lt;pre&gt;&lt;span class="normal"&gt; 1&lt;/span&gt;
&lt;span class="normal"&gt; 2&lt;/span&gt;
&lt;span class="normal"&gt; 3&lt;/span&gt;
&lt;span class="normal"&gt; 4&lt;/span&gt;
&lt;span class="normal"&gt; 5&lt;/span&gt;
&lt;span class="normal"&gt; 6&lt;/span&gt;
&lt;span class="normal"&gt; 7&lt;/span&gt;
&lt;span class="normal"&gt; 8&lt;/span&gt;
&lt;span class="normal"&gt; 9&lt;/span&gt;
&lt;span class="normal"&gt;10&lt;/span&gt;
&lt;span class="normal"&gt;11&lt;/span&gt;
&lt;span class="normal"&gt;12&lt;/span&gt;
&lt;span class="normal"&gt;13&lt;/span&gt;
&lt;span class="normal"&gt;14&lt;/span&gt;
&lt;span class="normal"&gt;15&lt;/span&gt;
&lt;span class="normal"&gt;16&lt;/span&gt;
&lt;span class="normal"&gt;17&lt;/span&gt;
&lt;span class="normal"&gt;18&lt;/span&gt;
&lt;span class="normal"&gt;19&lt;/span&gt;
&lt;span class="normal"&gt;20&lt;/span&gt;
&lt;span class="normal"&gt;21&lt;/span&gt;
&lt;span class="normal"&gt;22&lt;/span&gt;
&lt;span class="normal"&gt;23&lt;/span&gt;
&lt;span class="normal"&gt;24&lt;/span&gt;
&lt;span class="normal"&gt;25&lt;/span&gt;
&lt;span class="normal"&gt;26&lt;/span&gt;
&lt;span class="normal"&gt;27&lt;/span&gt;
&lt;span class="normal"&gt;28&lt;/span&gt;
&lt;span class="normal"&gt;29&lt;/span&gt;
&lt;span class="normal"&gt;30&lt;/span&gt;
&lt;span class="normal"&gt;31&lt;/span&gt;
&lt;span class="normal"&gt;32&lt;/span&gt;
&lt;span class="normal"&gt;33&lt;/span&gt;
&lt;span class="normal"&gt;34&lt;/span&gt;
&lt;span class="normal"&gt;35&lt;/span&gt;
&lt;span class="normal"&gt;36&lt;/span&gt;
&lt;span class="normal"&gt;37&lt;/span&gt;
&lt;span class="normal"&gt;38&lt;/span&gt;
&lt;span class="normal"&gt;39&lt;/span&gt;
&lt;span class="normal"&gt;40&lt;/span&gt;
&lt;span class="normal"&gt;41&lt;/span&gt;
&lt;span class="normal"&gt;42&lt;/span&gt;
&lt;span class="normal"&gt;43&lt;/span&gt;
&lt;span class="normal"&gt;44&lt;/span&gt;
&lt;span class="normal"&gt;45&lt;/span&gt;
&lt;span class="normal"&gt;46&lt;/span&gt;
&lt;span class="normal"&gt;47&lt;/span&gt;
&lt;span class="normal"&gt;48&lt;/span&gt;
&lt;span class="normal"&gt;49&lt;/span&gt;
&lt;span class="normal"&gt;50&lt;/span&gt;
&lt;span class="normal"&gt;51&lt;/span&gt;
&lt;span class="normal"&gt;52&lt;/span&gt;
&lt;span class="normal"&gt;53&lt;/span&gt;
&lt;span class="normal"&gt;54&lt;/span&gt;
&lt;span class="normal"&gt;55&lt;/span&gt;
&lt;span class="normal"&gt;56&lt;/span&gt;
&lt;span class="normal"&gt;57&lt;/span&gt;
&lt;span class="normal"&gt;58&lt;/span&gt;
&lt;span class="normal"&gt;59&lt;/span&gt;
&lt;span class="normal"&gt;60&lt;/span&gt;
&lt;span class="normal"&gt;61&lt;/span&gt;
&lt;span class="normal"&gt;62&lt;/span&gt;
&lt;span class="normal"&gt;63&lt;/span&gt;
&lt;span class="normal"&gt;64&lt;/span&gt;
&lt;span class="normal"&gt;65&lt;/span&gt;
&lt;span class="normal"&gt;66&lt;/span&gt;
&lt;span class="normal"&gt;67&lt;/span&gt;
&lt;span class="normal"&gt;68&lt;/span&gt;
&lt;span class="normal"&gt;69&lt;/span&gt;
&lt;span class="normal"&gt;70&lt;/span&gt;
&lt;span class="normal"&gt;71&lt;/span&gt;
&lt;span class="normal"&gt;72&lt;/span&gt;
&lt;span class="normal"&gt;73&lt;/span&gt;
&lt;span class="normal"&gt;74&lt;/span&gt;
&lt;span class="normal"&gt;75&lt;/span&gt;
&lt;span class="normal"&gt;76&lt;/span&gt;
&lt;span class="normal"&gt;77&lt;/span&gt;
&lt;span class="normal"&gt;78&lt;/span&gt;
&lt;span class="normal"&gt;79&lt;/span&gt;
&lt;span class="normal"&gt;80&lt;/span&gt;
&lt;span class="normal"&gt;81&lt;/span&gt;
&lt;span class="normal"&gt;82&lt;/span&gt;
&lt;span class="normal"&gt;83&lt;/span&gt;
&lt;span class="normal"&gt;84&lt;/span&gt;
&lt;span class="normal"&gt;85&lt;/span&gt;
&lt;span class="normal"&gt;86&lt;/span&gt;
&lt;span class="normal"&gt;87&lt;/span&gt;
&lt;span class="normal"&gt;88&lt;/span&gt;
&lt;span class="normal"&gt;89&lt;/span&gt;
&lt;span class="normal"&gt;90&lt;/span&gt;
&lt;span class="normal"&gt;91&lt;/span&gt;
&lt;span class="normal"&gt;92&lt;/span&gt;
&lt;span class="normal"&gt;93&lt;/span&gt;
&lt;span class="normal"&gt;94&lt;/span&gt;
&lt;span class="normal"&gt;95&lt;/span&gt;
&lt;span class="normal"&gt;96&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/td&gt;&lt;td class="code"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;    &lt;span class="c1"&gt;; Here, b and c contain dx and dy, the desired movement.&lt;/span&gt;

    &lt;span class="c1"&gt;; First, figure out which columns we might collide with.&lt;/span&gt;
    &lt;span class="c1"&gt;; The NEAREST is the first one to our right that we&amp;#39;re not&lt;/span&gt;
    &lt;span class="c1"&gt;; already overlapping, i.e. the one /after/ the one&lt;/span&gt;
    &lt;span class="c1"&gt;; containing our right edge.  That&amp;#39;s Exc(x + r) + 1.&lt;/span&gt;
    &lt;span class="c1"&gt;; The FURTHEST is the column that /will/ contain our right&lt;/span&gt;
    &lt;span class="c1"&gt;; edge.  That&amp;#39;s Exc(x + r + dx).&lt;/span&gt;
    &lt;span class="ow"&gt;ld&lt;/span&gt; &lt;span class="nb"&gt;hl&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;ANISE_RADIUS&lt;/span&gt;
    &lt;span class="c1"&gt;; Put the NEAREST column in d&lt;/span&gt;
    &lt;span class="ow"&gt;ld&lt;/span&gt; &lt;span class="nb"&gt;a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;anise_x&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;             &lt;span class="c1"&gt;; a = x&lt;/span&gt;
    &lt;span class="ow"&gt;add&lt;/span&gt; &lt;span class="nb"&gt;a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;hl&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;                 &lt;span class="c1"&gt;; a = x + r&lt;/span&gt;
    &lt;span class="ow"&gt;ld&lt;/span&gt; &lt;span class="nb"&gt;e&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;a&lt;/span&gt;                     &lt;span class="c1"&gt;; e = x + r&lt;/span&gt;
    &lt;span class="err"&gt;ToExclusiveCell&lt;/span&gt;
    &lt;span class="ow"&gt;inc&lt;/span&gt; &lt;span class="nb"&gt;a&lt;/span&gt;                       &lt;span class="c1"&gt;; a = Exc(x + r) + 1&lt;/span&gt;
    &lt;span class="ow"&gt;ld&lt;/span&gt; &lt;span class="nb"&gt;d&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;a&lt;/span&gt;                     &lt;span class="c1"&gt;; d = Exc(x + r) + 1&lt;/span&gt;
    &lt;span class="c1"&gt;; Put the FURTHEST column in e&lt;/span&gt;
    &lt;span class="ow"&gt;ld&lt;/span&gt; &lt;span class="nb"&gt;a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;e&lt;/span&gt;                     &lt;span class="c1"&gt;; a = x + r&lt;/span&gt;
    &lt;span class="ow"&gt;add&lt;/span&gt; &lt;span class="nb"&gt;a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;b&lt;/span&gt;                    &lt;span class="c1"&gt;; a = x + r + dx&lt;/span&gt;
    &lt;span class="err"&gt;ToExclusiveCell&lt;/span&gt;
    &lt;span class="ow"&gt;ld&lt;/span&gt; &lt;span class="nb"&gt;e&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;a&lt;/span&gt;                     &lt;span class="c1"&gt;; e = Exc(x + r + dx)&lt;/span&gt;

    &lt;span class="c1"&gt;; Loop over columns in [d, e].&lt;/span&gt;
    &lt;span class="c1"&gt;; If d &amp;gt; e, this movement doesn&amp;#39;t cross a grid line, so&lt;/span&gt;
    &lt;span class="c1"&gt;; nothing can stop us and we can skip all this logic.&lt;/span&gt;
    &lt;span class="ow"&gt;ld&lt;/span&gt; &lt;span class="nb"&gt;a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;e&lt;/span&gt;
    &lt;span class="ow"&gt;cp&lt;/span&gt; &lt;span class="nb"&gt;d&lt;/span&gt;
    &lt;span class="ow"&gt;jp&lt;/span&gt; &lt;span class="nb"&gt;c&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="err"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;done_x&lt;/span&gt;
    &lt;span class="c1"&gt;; We don&amp;#39;t need dx for now, so stash bc for some work space&lt;/span&gt;
    &lt;span class="ow"&gt;push&lt;/span&gt; &lt;span class="nb"&gt;bc&lt;/span&gt;
&lt;span class="nf"&gt;.x_row_scan:&lt;/span&gt;
    &lt;span class="c1"&gt;; For each column we might cross: check whether any of the&lt;/span&gt;
    &lt;span class="c1"&gt;; rows we span will block us.&lt;/span&gt;
    &lt;span class="c1"&gt;; Hm.  This code probably should&amp;#39;ve been outside the loop.&lt;/span&gt;
    &lt;span class="ow"&gt;ld&lt;/span&gt; &lt;span class="nb"&gt;a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;anise_y&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="ow"&gt;ld&lt;/span&gt; &lt;span class="nb"&gt;hl&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;ANISE_RADIUS&lt;/span&gt;
    &lt;span class="ow"&gt;sub&lt;/span&gt; &lt;span class="nb"&gt;a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;hl&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="err"&gt;ToInclusiveCell&lt;/span&gt;
    &lt;span class="ow"&gt;ld&lt;/span&gt; &lt;span class="nb"&gt;b&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;a&lt;/span&gt;                     &lt;span class="c1"&gt;; b = minimum y&lt;/span&gt;
    &lt;span class="ow"&gt;ld&lt;/span&gt; &lt;span class="nb"&gt;a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;anise_y&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="ow"&gt;add&lt;/span&gt; &lt;span class="nb"&gt;a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;hl&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="err"&gt;ToExclusiveCell&lt;/span&gt;
    &lt;span class="ow"&gt;ld&lt;/span&gt; &lt;span class="nb"&gt;c&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;a&lt;/span&gt;                     &lt;span class="c1"&gt;; c = maximum/current y&lt;/span&gt;

&lt;span class="nf"&gt;.x_column_scan:&lt;/span&gt;
    &lt;span class="c1"&gt;; Put the cell&amp;#39;s row and column in bc, and call a function&lt;/span&gt;
    &lt;span class="c1"&gt;; to check its &amp;quot;map flags&amp;quot;.  I&amp;#39;ll define that in a moment,&lt;/span&gt;
    &lt;span class="c1"&gt;; but for now I&amp;#39;ll assume that if bit 0 is set, that means&lt;/span&gt;
    &lt;span class="c1"&gt;; the cell is solid.&lt;/span&gt;
    &lt;span class="c1"&gt;; This is also why the inner loop counts down with c, not&lt;/span&gt;
    &lt;span class="c1"&gt;; up with b: get_cell_flags wants the y coord in c, and&lt;/span&gt;
    &lt;span class="c1"&gt;; this way, it&amp;#39;s already there!&lt;/span&gt;
    &lt;span class="ow"&gt;push&lt;/span&gt; &lt;span class="nb"&gt;bc&lt;/span&gt;
    &lt;span class="ow"&gt;ld&lt;/span&gt; &lt;span class="nb"&gt;b&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;d&lt;/span&gt;
    &lt;span class="ow"&gt;call&lt;/span&gt; &lt;span class="nv"&gt;get_cell_flags&lt;/span&gt;
    &lt;span class="ow"&gt;pop&lt;/span&gt; &lt;span class="nb"&gt;bc&lt;/span&gt;
    &lt;span class="c1"&gt;; If this produces zero, we can skip ahead&lt;/span&gt;
    &lt;span class="ow"&gt;and&lt;/span&gt; &lt;span class="nb"&gt;a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mh"&gt;$01&lt;/span&gt;
    &lt;span class="ow"&gt;jr&lt;/span&gt; &lt;span class="nv"&gt;z&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="err"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;not_blocked&lt;/span&gt;

    &lt;span class="c1"&gt;; We&amp;#39;re blocked!  Stop here.  Set x so that we&amp;#39;re butted&lt;/span&gt;
    &lt;span class="c1"&gt;; against this cell, which means subtract our radius from&lt;/span&gt;
    &lt;span class="c1"&gt;; its x coordinate.&lt;/span&gt;
    &lt;span class="c1"&gt;; Note that this can&amp;#39;t possibly move us further than dx,&lt;/span&gt;
    &lt;span class="c1"&gt;; because dx was /supposed/ to move us INTO this cell.&lt;/span&gt;
    &lt;span class="ow"&gt;ld&lt;/span&gt; &lt;span class="nb"&gt;a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;d&lt;/span&gt;
    &lt;span class="c1"&gt;; This is a /left/ shift three times, for cell -&amp;gt; pixel&lt;/span&gt;
    &lt;span class="ow"&gt;sla&lt;/span&gt; &lt;span class="nb"&gt;a&lt;/span&gt;
    &lt;span class="ow"&gt;sla&lt;/span&gt; &lt;span class="nb"&gt;a&lt;/span&gt;
    &lt;span class="ow"&gt;sla&lt;/span&gt; &lt;span class="nb"&gt;a&lt;/span&gt;
    &lt;span class="ow"&gt;sub&lt;/span&gt; &lt;span class="nb"&gt;a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;hl&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="ow"&gt;ld&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;anise_x&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="nb"&gt;a&lt;/span&gt;
    &lt;span class="c1"&gt;; Somewhat confusing pop, to restore dx and dy.&lt;/span&gt;
    &lt;span class="ow"&gt;pop&lt;/span&gt; &lt;span class="nb"&gt;bc&lt;/span&gt;
    &lt;span class="ow"&gt;jp&lt;/span&gt; &lt;span class="err"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;done_x&lt;/span&gt;

&lt;span class="nf"&gt;.not_blocked:&lt;/span&gt;
    &lt;span class="c1"&gt;; Not blocked, so loop to the next cell in this column&lt;/span&gt;
    &lt;span class="ow"&gt;dec&lt;/span&gt; &lt;span class="nb"&gt;c&lt;/span&gt;
    &lt;span class="ow"&gt;ld&lt;/span&gt; &lt;span class="nb"&gt;a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;c&lt;/span&gt;
    &lt;span class="ow"&gt;cp&lt;/span&gt; &lt;span class="nb"&gt;b&lt;/span&gt;
    &lt;span class="ow"&gt;jr&lt;/span&gt; &lt;span class="nv"&gt;nc&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="err"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;x_column_scan&lt;/span&gt;

    &lt;span class="c1"&gt;; Finished checking one column successfully, so continue on&lt;/span&gt;
    &lt;span class="c1"&gt;; to the next one&lt;/span&gt;
    &lt;span class="ow"&gt;inc&lt;/span&gt; &lt;span class="nb"&gt;d&lt;/span&gt;
    &lt;span class="ow"&gt;ld&lt;/span&gt; &lt;span class="nb"&gt;a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;e&lt;/span&gt;
    &lt;span class="ow"&gt;cp&lt;/span&gt; &lt;span class="nb"&gt;d&lt;/span&gt;
    &lt;span class="ow"&gt;jr&lt;/span&gt; &lt;span class="nv"&gt;nc&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="err"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;x_row_scan&lt;/span&gt;

    &lt;span class="c1"&gt;; Done, and we never hit anything!  Update our position to&lt;/span&gt;
    &lt;span class="c1"&gt;; what was requested&lt;/span&gt;
    &lt;span class="ow"&gt;pop&lt;/span&gt; &lt;span class="nb"&gt;bc&lt;/span&gt;
    &lt;span class="ow"&gt;ld&lt;/span&gt; &lt;span class="nb"&gt;a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;anise_x&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="ow"&gt;add&lt;/span&gt; &lt;span class="nb"&gt;a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;b&lt;/span&gt;
    &lt;span class="ow"&gt;ld&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;anise_x&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="nb"&gt;a&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;p&gt;I&amp;#8217;ve also gotta implement &lt;code&gt;get_cell_flags&lt;/code&gt;, which is slightly uglier than I&amp;nbsp;anticipated.&lt;/p&gt;
&lt;table class="highlighttable"&gt;&lt;tr&gt;&lt;td class="linenos"&gt;&lt;div class="linenodiv"&gt;&lt;pre&gt;&lt;span class="normal"&gt; 1&lt;/span&gt;
&lt;span class="normal"&gt; 2&lt;/span&gt;
&lt;span class="normal"&gt; 3&lt;/span&gt;
&lt;span class="normal"&gt; 4&lt;/span&gt;
&lt;span class="normal"&gt; 5&lt;/span&gt;
&lt;span class="normal"&gt; 6&lt;/span&gt;
&lt;span class="normal"&gt; 7&lt;/span&gt;
&lt;span class="normal"&gt; 8&lt;/span&gt;
&lt;span class="normal"&gt; 9&lt;/span&gt;
&lt;span class="normal"&gt;10&lt;/span&gt;
&lt;span class="normal"&gt;11&lt;/span&gt;
&lt;span class="normal"&gt;12&lt;/span&gt;
&lt;span class="normal"&gt;13&lt;/span&gt;
&lt;span class="normal"&gt;14&lt;/span&gt;
&lt;span class="normal"&gt;15&lt;/span&gt;
&lt;span class="normal"&gt;16&lt;/span&gt;
&lt;span class="normal"&gt;17&lt;/span&gt;
&lt;span class="normal"&gt;18&lt;/span&gt;
&lt;span class="normal"&gt;19&lt;/span&gt;
&lt;span class="normal"&gt;20&lt;/span&gt;
&lt;span class="normal"&gt;21&lt;/span&gt;
&lt;span class="normal"&gt;22&lt;/span&gt;
&lt;span class="normal"&gt;23&lt;/span&gt;
&lt;span class="normal"&gt;24&lt;/span&gt;
&lt;span class="normal"&gt;25&lt;/span&gt;
&lt;span class="normal"&gt;26&lt;/span&gt;
&lt;span class="normal"&gt;27&lt;/span&gt;
&lt;span class="normal"&gt;28&lt;/span&gt;
&lt;span class="normal"&gt;29&lt;/span&gt;
&lt;span class="normal"&gt;30&lt;/span&gt;
&lt;span class="normal"&gt;31&lt;/span&gt;
&lt;span class="normal"&gt;32&lt;/span&gt;
&lt;span class="normal"&gt;33&lt;/span&gt;
&lt;span class="normal"&gt;34&lt;/span&gt;
&lt;span class="normal"&gt;35&lt;/span&gt;
&lt;span class="normal"&gt;36&lt;/span&gt;
&lt;span class="normal"&gt;37&lt;/span&gt;
&lt;span class="normal"&gt;38&lt;/span&gt;
&lt;span class="normal"&gt;39&lt;/span&gt;
&lt;span class="normal"&gt;40&lt;/span&gt;
&lt;span class="normal"&gt;41&lt;/span&gt;
&lt;span class="normal"&gt;42&lt;/span&gt;
&lt;span class="normal"&gt;43&lt;/span&gt;
&lt;span class="normal"&gt;44&lt;/span&gt;
&lt;span class="normal"&gt;45&lt;/span&gt;
&lt;span class="normal"&gt;46&lt;/span&gt;
&lt;span class="normal"&gt;47&lt;/span&gt;
&lt;span class="normal"&gt;48&lt;/span&gt;
&lt;span class="normal"&gt;49&lt;/span&gt;
&lt;span class="normal"&gt;50&lt;/span&gt;
&lt;span class="normal"&gt;51&lt;/span&gt;
&lt;span class="normal"&gt;52&lt;/span&gt;
&lt;span class="normal"&gt;53&lt;/span&gt;
&lt;span class="normal"&gt;54&lt;/span&gt;
&lt;span class="normal"&gt;55&lt;/span&gt;
&lt;span class="normal"&gt;56&lt;/span&gt;
&lt;span class="normal"&gt;57&lt;/span&gt;
&lt;span class="normal"&gt;58&lt;/span&gt;
&lt;span class="normal"&gt;59&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/td&gt;&lt;td class="code"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="c1"&gt;; Fetches properties for the map cell at the given coordinates.&lt;/span&gt;
&lt;span class="c1"&gt;; In: bc = x/y coordinates&lt;/span&gt;
&lt;span class="c1"&gt;; Out: a = flags&lt;/span&gt;
&lt;span class="nf"&gt;get_cell_flags:&lt;/span&gt;
    &lt;span class="ow"&gt;push&lt;/span&gt; &lt;span class="nb"&gt;hl&lt;/span&gt;
    &lt;span class="ow"&gt;push&lt;/span&gt; &lt;span class="nb"&gt;de&lt;/span&gt;
    &lt;span class="c1"&gt;; I have to figure out what char is at these coordinates,&lt;/span&gt;
    &lt;span class="c1"&gt;; which means consulting the map, which means doing math.&lt;/span&gt;
    &lt;span class="c1"&gt;; The map is currently 16 (big) tiles wide, or 32 chars,&lt;/span&gt;
    &lt;span class="c1"&gt;; so the byte for the indicated char is at b + 32 * c.&lt;/span&gt;
    &lt;span class="ow"&gt;ld&lt;/span&gt; &lt;span class="nb"&gt;hl&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;TEST_MAP_1&lt;/span&gt;
    &lt;span class="c1"&gt;; Add x coordinate.  hl is 16 bits, so extend b to 16 bits&lt;/span&gt;
    &lt;span class="c1"&gt;; using the d and e registers separately, then add.&lt;/span&gt;
    &lt;span class="ow"&gt;ld&lt;/span&gt; &lt;span class="nb"&gt;d&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;0&lt;/span&gt;
    &lt;span class="ow"&gt;ld&lt;/span&gt; &lt;span class="nb"&gt;e&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;b&lt;/span&gt;
    &lt;span class="ow"&gt;add&lt;/span&gt; &lt;span class="nb"&gt;hl&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;de&lt;/span&gt;
    &lt;span class="c1"&gt;; Add y coordinate, with stride of 32, which we can do&lt;/span&gt;
    &lt;span class="c1"&gt;; without multiplying by shifting left 5.  Alas, there are&lt;/span&gt;
    &lt;span class="c1"&gt;; no 16-bit shifts, so I have to do this by hand.&lt;/span&gt;
    &lt;span class="c1"&gt;; First get the 5 high bits by copying y into d, then&lt;/span&gt;
    &lt;span class="c1"&gt;; shifting the 3 low bits off the right end.&lt;/span&gt;
    &lt;span class="ow"&gt;ld&lt;/span&gt; &lt;span class="nb"&gt;d&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;c&lt;/span&gt;
    &lt;span class="ow"&gt;srl&lt;/span&gt; &lt;span class="nb"&gt;d&lt;/span&gt;
    &lt;span class="ow"&gt;srl&lt;/span&gt; &lt;span class="nb"&gt;d&lt;/span&gt;
    &lt;span class="ow"&gt;srl&lt;/span&gt; &lt;span class="nb"&gt;d&lt;/span&gt;
    &lt;span class="c1"&gt;; Then get the low 3 bits into the high 3 by swapping,&lt;/span&gt;
    &lt;span class="c1"&gt;; shifting, and masking them off.&lt;/span&gt;
    &lt;span class="ow"&gt;ld&lt;/span&gt; &lt;span class="nb"&gt;a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;c&lt;/span&gt;
    &lt;span class="ow"&gt;swap&lt;/span&gt; &lt;span class="nb"&gt;a&lt;/span&gt;
    &lt;span class="ow"&gt;sla&lt;/span&gt; &lt;span class="nb"&gt;a&lt;/span&gt;
    &lt;span class="ow"&gt;and&lt;/span&gt; &lt;span class="nb"&gt;a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mh"&gt;$e0&lt;/span&gt;
    &lt;span class="ow"&gt;ld&lt;/span&gt; &lt;span class="nb"&gt;e&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;a&lt;/span&gt;
    &lt;span class="c1"&gt;; Not sure that was really any faster than just shifting&lt;/span&gt;
    &lt;span class="c1"&gt;; left through the carry flag 5 times.  Oh well.  Add.&lt;/span&gt;
    &lt;span class="ow"&gt;add&lt;/span&gt; &lt;span class="nb"&gt;hl&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;de&lt;/span&gt;

    &lt;span class="c1"&gt;; At last, we know the char.  I don&amp;#39;t have real flags at&lt;/span&gt;
    &lt;span class="c1"&gt;; the moment, so I just hardcoded the four chars that make&lt;/span&gt;
    &lt;span class="c1"&gt;; up the small rock tile.&lt;/span&gt;
    &lt;span class="ow"&gt;ld&lt;/span&gt; &lt;span class="nb"&gt;a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;hl&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="ow"&gt;cp&lt;/span&gt; &lt;span class="nb"&gt;a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;2&lt;/span&gt;
    &lt;span class="ow"&gt;jr&lt;/span&gt; &lt;span class="nv"&gt;z&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="err"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;blocking&lt;/span&gt;
    &lt;span class="ow"&gt;cp&lt;/span&gt; &lt;span class="nb"&gt;a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;3&lt;/span&gt;
    &lt;span class="ow"&gt;jr&lt;/span&gt; &lt;span class="nv"&gt;z&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="err"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;blocking&lt;/span&gt;
    &lt;span class="ow"&gt;cp&lt;/span&gt; &lt;span class="nb"&gt;a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;12&lt;/span&gt;
    &lt;span class="ow"&gt;jr&lt;/span&gt; &lt;span class="nv"&gt;z&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="err"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;blocking&lt;/span&gt;
    &lt;span class="ow"&gt;cp&lt;/span&gt; &lt;span class="nb"&gt;a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;13&lt;/span&gt;
    &lt;span class="ow"&gt;jr&lt;/span&gt; &lt;span class="nv"&gt;z&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="err"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;blocking&lt;/span&gt;
    &lt;span class="ow"&gt;jr&lt;/span&gt; &lt;span class="err"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;not_blocking&lt;/span&gt;
    &lt;span class="c1"&gt;; The rest should not be too surprising.&lt;/span&gt;
&lt;span class="nf"&gt;.blocking:&lt;/span&gt;
    &lt;span class="ow"&gt;ld&lt;/span&gt; &lt;span class="nb"&gt;a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;1&lt;/span&gt;
    &lt;span class="ow"&gt;jr&lt;/span&gt; &lt;span class="err"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;done&lt;/span&gt;
&lt;span class="nf"&gt;.not_blocking:&lt;/span&gt;
    &lt;span class="ow"&gt;xor&lt;/span&gt; &lt;span class="nb"&gt;a&lt;/span&gt;
&lt;span class="nf"&gt;.done:&lt;/span&gt;
    &lt;span class="ow"&gt;pop&lt;/span&gt; &lt;span class="nb"&gt;de&lt;/span&gt;
    &lt;span class="ow"&gt;pop&lt;/span&gt; &lt;span class="nb"&gt;hl&lt;/span&gt;
    &lt;span class="ow"&gt;ret&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;p&gt;And that&amp;#8217;s&amp;nbsp;it!&lt;/p&gt;
&lt;h2 id="thats-not-it"&gt;&lt;a class="toclink" href="#thats-not-it"&gt;That's not&amp;nbsp;it&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;The code I wrote only applies when moving &lt;em&gt;right&lt;/em&gt;.  It doesn&amp;#8217;t handle moving left at&amp;nbsp;all.&lt;/p&gt;
&lt;p&gt;And here I run into a downside of continuous collision, at least in this particular case.  Because of the special behavior of right/bottom edges, I can&amp;#8217;t simply flip a sign to make this code work for leftwards movement as well.  For example, the set of columns I might cross going rightwards is calculated &lt;em&gt;exclusively&lt;/em&gt;, because my right edge is the one in front…  but if I&amp;#8217;m moving leftwards, it&amp;#8217;s calculated &lt;em&gt;inclusively&lt;/em&gt;.  Those columns are also in reverse order and thus need iterating over backwards, so an &lt;code&gt;inc&lt;/code&gt; somewhere becomes a &lt;code&gt;dec&lt;/code&gt;, and so&amp;nbsp;on.&lt;/p&gt;
&lt;p&gt;I have two uncomfortable options for handling this.  One is to add all the required conditional tests and jumps, but that adds a decent &lt;span class="caps"&gt;CPU&lt;/span&gt; cost to code that&amp;#8217;s fairly small and potentially very hot, &lt;em&gt;and&lt;/em&gt; complicates code that&amp;#8217;s a bit dense and delicate to begin with.  The other option is to &lt;em&gt;copy-paste&lt;/em&gt; the whole shebang and adjust it as needed to go&amp;nbsp;leftwards.&lt;/p&gt;
&lt;p&gt;Guess which I&amp;nbsp;did!&lt;/p&gt;
&lt;table class="highlighttable"&gt;&lt;tr&gt;&lt;td class="linenos"&gt;&lt;div class="linenodiv"&gt;&lt;pre&gt;&lt;span class="normal"&gt;1&lt;/span&gt;
&lt;span class="normal"&gt;2&lt;/span&gt;
&lt;span class="normal"&gt;3&lt;/span&gt;
&lt;span class="normal"&gt;4&lt;/span&gt;
&lt;span class="normal"&gt;5&lt;/span&gt;
&lt;span class="normal"&gt;6&lt;/span&gt;
&lt;span class="normal"&gt;7&lt;/span&gt;
&lt;span class="normal"&gt;8&lt;/span&gt;
&lt;span class="normal"&gt;9&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/td&gt;&lt;td class="code"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;    &lt;span class="ow"&gt;ld&lt;/span&gt; &lt;span class="nb"&gt;a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;b&lt;/span&gt;
    &lt;span class="ow"&gt;cp&lt;/span&gt; &lt;span class="nb"&gt;a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mh"&gt;$80&lt;/span&gt;
    &lt;span class="ow"&gt;jp&lt;/span&gt; &lt;span class="nv"&gt;nc&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="err"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;negative_x&lt;/span&gt;
&lt;span class="nf"&gt;.positive_x:&lt;/span&gt;
    &lt;span class="c1"&gt;; ... everything above ...&lt;/span&gt;
    &lt;span class="ow"&gt;jp&lt;/span&gt; &lt;span class="err"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;done_x&lt;/span&gt;
&lt;span class="nf"&gt;.negative_x:&lt;/span&gt;
    &lt;span class="c1"&gt;; ... everything above, flipped ...&lt;/span&gt;
&lt;span class="nf"&gt;.done_x:&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;p&gt;Ugh.  Don&amp;#8217;t worry, though — it gets worse later&amp;nbsp;on!&lt;/p&gt;
&lt;p&gt;I could copy-paste for y movement too and give myself a total of &lt;em&gt;four&lt;/em&gt; blocks of similar code, but I&amp;#8217;ll hold off on that for&amp;nbsp;now.&lt;/p&gt;
&lt;p&gt;…&lt;/p&gt;
&lt;p&gt;Ah.&lt;/p&gt;
&lt;p&gt;You want the payoff, don&amp;#8217;t&amp;nbsp;you.&lt;/p&gt;
&lt;p&gt;Well, I&amp;#8217;m warning you now: the next post gets much hairier, and if I show you a &lt;span class="caps"&gt;GIF&lt;/span&gt; &lt;em&gt;now&lt;/em&gt;, there won&amp;#8217;t be any payoff next&amp;nbsp;time.&lt;/p&gt;
&lt;p&gt;You sure?&amp;nbsp;Really?&lt;/p&gt;
&lt;p&gt;No going&amp;nbsp;back!&lt;/p&gt;
&lt;div class="prose-full-illustration"&gt;
&lt;img src="https://eev.ee/media/cheezball/07f-anise-blocked.gif" alt="Star Anise walking around, but not through a rock!"&gt;
&lt;/div&gt;

&lt;p&gt;I admit, this was &lt;em&gt;pretty damn satisfying&lt;/em&gt; the first time it actually worked.  Collision detection is a pain in the ass, but it&amp;#8217;s the first step to making a game feel like a &lt;em&gt;game&lt;/em&gt;.  Games are about working within limitations, after&amp;nbsp;all!&lt;/p&gt;
&lt;h2 id="an-aside-debugging"&gt;&lt;a class="toclink" href="#an-aside-debugging"&gt;An aside:&amp;nbsp;debugging&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;I&amp;#8217;ve made this adventure seem much &lt;em&gt;easier&lt;/em&gt; than it actually was by eliding all the mistakes.  I made &lt;em&gt;a lot&lt;/em&gt; of mistakes, and as I said upfront, it can be very difficult to notice heisenbugs or figure out exactly what&amp;#8217;s causing&amp;nbsp;them.&lt;/p&gt;
&lt;p&gt;One thing that helped &lt;em&gt;tremendously&lt;/em&gt; near the beginning was to hack Star Anise to have a fourth sprite: a solid black 6×6 square under his feet.  That let me see where he was actually &lt;em&gt;supposed&lt;/em&gt; to be able to stand.  Highly recommend it.  All I did was copy/paste everywhere that mentioned his sprites to add a fourth one, and position it centered under his&amp;nbsp;feet.&lt;/p&gt;
&lt;p&gt;(On any other system, I&amp;#8217;d just draw collision rectangles everywhere, but the Game Boy is sprite-based so that&amp;#8217;s not really gonna&amp;nbsp;fly.)&lt;/p&gt;
&lt;p&gt;I also had pretty good success with writing intermediate values to unused bytes in &lt;span class="caps"&gt;RAM&lt;/span&gt;, so I could inspect them in mGBA&amp;#8217;s memory viewer even after the movement was finished.  And of course, as an absolute last resort, bgb has an interactive graphical debugger.  (Nothing against bgb per se; I just prefer not to rely on closed-source software running in Wine if I can at all get away with&amp;nbsp;it.)&lt;/p&gt;
&lt;h2 id="to-be-continued"&gt;&lt;a class="toclink" href="#to-be-continued"&gt;To be&amp;nbsp;continued&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Obviously, this isn&amp;#8217;t anywhere near done.  There&amp;#8217;s no concept of collision with other entities, and before that&amp;#8217;s even a possibility, I need a concept of &lt;em&gt;other entities&lt;/em&gt;.  I left myself a long trail of do-it-laters.  There are even risks of overflow and underflow in a couple places, which I didn&amp;#8217;t bother pointing out because I completely overhaul this code&amp;nbsp;later.&lt;/p&gt;
&lt;p&gt;But it&amp;#8217;s a big step forward, and now I just need a few more big steps forward.  (I say, four months later, long after all those steps are&amp;nbsp;done.)&lt;/p&gt;
&lt;p&gt;I already have some future ideas in mind, like: what if a map tile weren&amp;#8217;t completely solid, but had its &lt;em&gt;own&lt;/em&gt; radius?  Could I implement corner cutting, where the game gently guides you if you get stuck on a corner by only a single pixel?  What about having tiles that are 45° angles, just to cut down on the overt squareness of the&amp;nbsp;map?&lt;/p&gt;
&lt;p&gt;Well.  Maybe, you know,&amp;nbsp;later.&lt;/p&gt;
&lt;p&gt;Anyway, that brings us up to &lt;a href="https://github.com/eevee/anise-cheezball-rising/commit/da74782e8b36413cfd26ce4b3b4ce583535ce7cf"&gt;commit da7478e&lt;/a&gt;.  It&amp;#8217;s all downhill from&amp;nbsp;here.&lt;/p&gt;
&lt;p&gt;Next time: &lt;em&gt;more collision detection, and fixed-point arithmetic&lt;/em&gt;!&lt;/p&gt;
&lt;script type="text/javascript"&gt;if (!document.getElementById('mathjaxscript_pelican_#%@#$@#')) {
    var align = "center",
        indent = "0em",
        linebreak = "false";

    if (false) {
        align = (screen.width &lt; 768) ? "left" : align;
        indent = (screen.width &lt; 768) ? "0em" : indent;
        linebreak = (screen.width &lt; 768) ? 'true' : linebreak;
    }

    var mathjaxscript = document.createElement('script');
    mathjaxscript.id = 'mathjaxscript_pelican_#%@#$@#';
    mathjaxscript.type = 'text/javascript';
    mathjaxscript.src = 'https://cdnjs.cloudflare.com/ajax/libs/mathjax/2.7.3/latest.js?config=TeX-AMS-MML_HTMLorMML';

    var configscript = document.createElement('script');
    configscript.type = 'text/x-mathjax-config';
    configscript[(window.opera ? "innerHTML" : "text")] =
        "MathJax.Hub.Config({" +
        "    config: ['MMLorHTML.js']," +
        "    TeX: { extensions: ['AMSmath.js','AMSsymbols.js','noErrors.js','noUndefined.js'], equationNumbers: { autoNumber: 'none' } }," +
        "    jax: ['input/TeX','input/MathML','output/HTML-CSS']," +
        "    extensions: ['tex2jax.js','mml2jax.js','MathMenu.js','MathZoom.js']," +
        "    displayAlign: '"+ align +"'," +
        "    displayIndent: '"+ indent +"'," +
        "    showMathMenu: true," +
        "    messageStyle: 'normal'," +
        "    tex2jax: { " +
        "        inlineMath: [ ['\\\\(','\\\\)'] ], " +
        "        displayMath: [ ['$$','$$'] ]," +
        "        processEscapes: true," +
        "        preview: 'TeX'," +
        "    }, " +
        "    'HTML-CSS': { " +
        "        availableFonts: ['STIX', 'TeX']," +
        "        preferredFont: 'STIX'," +
        "        styles: { '.MathJax_Display, .MathJax .mo, .MathJax .mi, .MathJax .mn': {color: 'inherit ! important'} }," +
        "        linebreaks: { automatic: "+ linebreak +", width: '90% container' }," +
        "    }, " +
        "}); " +
        "if ('default' !== 'default') {" +
            "MathJax.Hub.Register.StartupHook('HTML-CSS Jax Ready',function () {" +
                "var VARIANT = MathJax.OutputJax['HTML-CSS'].FONTDATA.VARIANT;" +
                "VARIANT['normal'].fonts.unshift('MathJax_default');" +
                "VARIANT['bold'].fonts.unshift('MathJax_default-bold');" +
                "VARIANT['italic'].fonts.unshift('MathJax_default-italic');" +
                "VARIANT['-tex-mathit'].fonts.unshift('MathJax_default-italic');" +
            "});" +
            "MathJax.Hub.Register.StartupHook('SVG Jax Ready',function () {" +
                "var VARIANT = MathJax.OutputJax.SVG.FONTDATA.VARIANT;" +
                "VARIANT['normal'].fonts.unshift('MathJax_default');" +
                "VARIANT['bold'].fonts.unshift('MathJax_default-bold');" +
                "VARIANT['italic'].fonts.unshift('MathJax_default-italic');" +
                "VARIANT['-tex-mathit'].fonts.unshift('MathJax_default-italic');" +
            "});" +
        "}";

    (document.body || document.getElementsByTagName('head')[0]).appendChild(configscript);
    (document.body || document.getElementsByTagName('head')[0]).appendChild(mathjaxscript);
}
&lt;/script&gt;</content><category term="blog"></category><category term="tech"></category><category term="gamedev"></category><category term="cheezball rising"></category></entry><entry><title>Cheezball Rising: Opening a dialogue</title><link href="https://eev.ee/blog/2018/10/09/cheezball-rising-opening-a-dialogue/" rel="alternate"></link><published>2018-10-09T09:07:00-07:00</published><updated>2018-10-09T09:07:00-07:00</updated><author><name>Eevee</name></author><id>tag:eev.ee,2018-10-09:/blog/2018/10/09/cheezball-rising-opening-a-dialogue/</id><summary type="html">&lt;p&gt;This is a series about &lt;a href="https://github.com/eevee/anise-cheezball-rising"&gt;&lt;strong&gt;Star Anise Chronicles: Cheezball Rising&lt;/strong&gt;&lt;/a&gt;, an expansive adventure game about my cat for the Game Boy Color.  Follow along as I struggle to make something with this bleeding-edge console!&lt;/p&gt;
&lt;p&gt;GitHub has &lt;a href="https://github.com/eevee/anise-cheezball-rising/releases"&gt;intermittent prebuilt ROMs&lt;/a&gt;, or you can get them a week early &lt;a href="https://www.patreon.com/eevee/posts?tag=cheezball%20rising"&gt;on Patreon&lt;/a&gt; if you pledge $4.  More details in the &lt;a href="https://github.com/eevee/anise-cheezball-rising"&gt;&lt;span class="caps"&gt;README&lt;/span&gt;&lt;/a&gt;!&lt;/p&gt;
&lt;hr/&gt;
&lt;p&gt;In this issue, I draw some text!&lt;/p&gt;
&lt;p&gt;Previously: &lt;a href="https://eev.ee/blog/2018/09/06/cheezball-rising-resounding-failure/"&gt;I get a Game Boy to meow&lt;/a&gt;.&lt;br/&gt;
Next: &lt;a href="https://eev.ee/blog/2018/11/28/cheezball-rising-collision-detection-part-1/"&gt;collision detection&lt;/a&gt;, &lt;em&gt;ohh nooo&lt;/em&gt;…&lt;/p&gt;
</summary><content type="html">&lt;p&gt;This is a series about &lt;a href="https://github.com/eevee/anise-cheezball-rising"&gt;&lt;strong&gt;Star Anise Chronicles: Cheezball Rising&lt;/strong&gt;&lt;/a&gt;, an expansive adventure game about my cat for the Game Boy Color.  Follow along as I struggle to make something with this bleeding-edge&amp;nbsp;console!&lt;/p&gt;
&lt;p&gt;GitHub has &lt;a href="https://github.com/eevee/anise-cheezball-rising/releases"&gt;intermittent prebuilt ROMs&lt;/a&gt;, or you can get them a week early &lt;a href="https://www.patreon.com/eevee/posts?tag=cheezball%20rising"&gt;on Patreon&lt;/a&gt; if you pledge $4.  More details in the &lt;a href="https://github.com/eevee/anise-cheezball-rising"&gt;&lt;span class="caps"&gt;README&lt;/span&gt;&lt;/a&gt;!&lt;/p&gt;
&lt;hr /&gt;
&lt;p&gt;In this issue, I draw some&amp;nbsp;text!&lt;/p&gt;
&lt;p&gt;Previously: &lt;a href="https://eev.ee/blog/2018/09/06/cheezball-rising-resounding-failure/"&gt;I get a Game Boy to meow&lt;/a&gt;.&lt;br /&gt;
Next: &lt;a href="https://eev.ee/blog/2018/11/28/cheezball-rising-collision-detection-part-1/"&gt;collision detection&lt;/a&gt;, &lt;em&gt;ohh nooo&lt;/em&gt;…&lt;/p&gt;


&lt;h2 id="recap"&gt;&lt;a class="toclink" href="#recap"&gt;Recap&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;The previous episode was a diversion (and left an open problem that I only solved &lt;em&gt;after&lt;/em&gt; writing it), so the actual state of the game is&amp;nbsp;unchanged.&lt;/p&gt;
&lt;div class="prose-full-illustration"&gt;
&lt;img src="https://eev.ee/media/cheezball/04l-go-anise-go-again.gif" alt="Star Anise walking around a moon environment in-game, animated in all four directions"&gt;
&lt;/div&gt;

&lt;p&gt;Where should I &lt;em&gt;actually&lt;/em&gt; go from here?  Collision detection is an obvious place, but that&amp;#8217;s &lt;em&gt;hard&lt;/em&gt;.  Let&amp;#8217;s start with something a little easier: displaying scrolling dialogue text.  This is likely to be a dialogue-heavy game, so I might as well get started on that&amp;nbsp;now.&lt;/p&gt;
&lt;h2 id="planning"&gt;&lt;a class="toclink" href="#planning"&gt;Planning&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;On any other platform, I&amp;#8217;d dive right into it: draw a box on the screen somewhere, fill it with&amp;nbsp;text.&lt;/p&gt;
&lt;p&gt;On the Game Boy, it&amp;#8217;s not quite that simple.  I can&amp;#8217;t just write text to the screen; I can only place tiles and&amp;nbsp;sprites.&lt;/p&gt;
&lt;p&gt;Let&amp;#8217;s look at how, say, Pokémon Yellow handles its&amp;nbsp;menu.&lt;/p&gt;
&lt;div class="prose-full-illustration"&gt;
&lt;img src="https://eev.ee/media/cheezball/06a-pokemon-text-example.png" alt="Pokémon Yellow with several levels of menu open"&gt;
&lt;/div&gt;

&lt;p&gt;This looks — &lt;em&gt;feels&lt;/em&gt; — like it&amp;#8217;s being drawn on top of the map, and that sub-menus open on top of other menus.  But it&amp;#8217;s all an illusion!  There&amp;#8217;s no &amp;#8220;on top&amp;#8221; here.  This is a completely flat image made up of tiles, like anything&amp;nbsp;else.&lt;/p&gt;
&lt;div class="prose-full-illustration"&gt;
&lt;img src="https://eev.ee/media/cheezball/06b-pokemon-text-grid.png" alt="The same screenshot, scaled up, with a grid showing the edges of tiles"&gt;
&lt;/div&gt;

&lt;p&gt;This is why Pokémon has such a conspicuously blocky font: all the glyphs are drawn to fit in a single 8×8 char, so &amp;#8220;drawing&amp;#8221; text is as simple as mapping letters to char indexes and drawing them onto the background.  The map and the menu are all on the same layer, and the game simply redraws whatever was underneath when you close something.  Part of the illusion is that the game is clever enough to hide any sprites that &lt;em&gt;would&lt;/em&gt; overlap the menu — because sprites would draw on top!  (The Game Boy Color has some twiddles for controlling this layering, but Yellow was originally designed for the monochrome Game&amp;nbsp;Boy.)&lt;/p&gt;
&lt;p&gt;A &lt;strong&gt;critical&lt;/strong&gt; reason that this actually works is that in Pokémon, the camera is &lt;em&gt;always&lt;/em&gt; aligned to the grid.  It scrolls smoothly while you&amp;#8217;re walking, but you can&amp;#8217;t actually open the menu (or pick up an item, or talk to someone, or do anything else that might show text) until you&amp;#8217;ve stopped moving.  If you could, the menu would be misaligned, because it&amp;#8217;s part of the same grid as the&amp;nbsp;map!&lt;/p&gt;
&lt;p&gt;This poses a slight problem for &lt;em&gt;my&lt;/em&gt; game.  Star Anise isn&amp;#8217;t locked to the grid like the Pokémon protagonist is, and unlike Link&amp;#8217;s Awakening, I do want to have areas larger than the screen that can scroll around&amp;nbsp;freely.&lt;/p&gt;
&lt;p&gt;I know offhand that there are a couple ways to do this.  One is the &lt;em&gt;window&lt;/em&gt;, an optional extra opaque layer that draws on top of the background, with its top-left corner anchored to any point on the screen.  Another is to change some display registers in the &lt;em&gt;middle&lt;/em&gt; of the screen redrawing.  If you&amp;#8217;re thinking of any games with a status bar at the bottom or right, chances are they use the window; games with a status bar at the top have to use display register&amp;nbsp;tricks.&lt;/p&gt;
&lt;p&gt;But I don&amp;#8217;t want to worry about any of this right now, before I even have text drawing.  I know it&amp;#8217;s &lt;em&gt;possible&lt;/em&gt;, so I&amp;#8217;ll deal with it later.  For now, drawing directly onto the background is good&amp;nbsp;enough.&lt;/p&gt;
&lt;h3 id="font-decisions"&gt;&lt;a class="toclink" href="#font-decisions"&gt;Font&amp;nbsp;decisions&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Let&amp;#8217;s get back to the font itself.  I&amp;#8217;m not in &lt;em&gt;love&lt;/em&gt; with the 8×8 aesthetic; what are my other options?  I do like the text in Oracle of Ages, so let&amp;#8217;s have a look at&amp;nbsp;that:&lt;/p&gt;
&lt;div class="prose-full-illustration"&gt;
&lt;img src="https://eev.ee/media/cheezball/06c-oracle-of-ages-text-grid.png" alt="Oracle of Ages, also scaled up with a grid, showing its taller text"&gt;
&lt;/div&gt;

&lt;p&gt;Ah, this is the same approach again, except that letters are now allowed to peek up into the char above.  So these are 8×16, but the letters all occupy a box that&amp;#8217;s more like 6×9, offering much more familiar proportions.  Oracle of Ages is designed for the Game Boy Color, which has twice as much char storage space, so it makes sense that they&amp;#8217;d take advantage of it for text like&amp;nbsp;this.&lt;/p&gt;
&lt;p&gt;It&amp;#8217;s not &lt;em&gt;bad&lt;/em&gt;, but the space it affords is still fairly…  limited.  Only 16 letters will fit in a line, just as with Pokémon, and that means a lot of carefully wording things to be short and use mostly short words as well.  That&amp;#8217;s not gonna cut it for the amount of dialogue I expect to&amp;nbsp;have.&lt;/p&gt;
&lt;p&gt;(You may be wondering, as I did, how Oracle pulled off this grid-aligned textbox.  In small buildings and the overworld, each room is exactly the size of the screen, so there&amp;#8217;s no scrolling and no worry about misaligned text.  But how does the game handle showing text inside a dungeon, where a room is bigger than the screen and can scroll freely?  The answer is: &lt;a href="https://twitter.com/eevee/status/1038432032064339968"&gt;it doesn&amp;#8217;t&lt;/a&gt;!  The textbox is just placed &lt;em&gt;as close as possible&lt;/em&gt; to the position shown in this screenshot, so the edges might be misaligned by up to 4 pixels.  In 20 years, I never noticed this until I thought to check how they were handling it.  I&amp;#8217;m sure there&amp;#8217;s a lesson,&amp;nbsp;here.)&lt;/p&gt;
&lt;p&gt;What other options do I have?  It seems like I&amp;#8217;m limited to multiples of 8 here, surely.  (The answer may be obvious to some of you, but shh, don&amp;#8217;t read&amp;nbsp;ahead.)&lt;/p&gt;
&lt;p&gt;The answer lies in the very last game released for the Game Boy Color: Harry Potter and the Chamber of Secrets.  Whatever deep secrets were learned during the Game Boy&amp;#8217;s lifetime will surely be encapsulated within this, er, movie tie-in&amp;nbsp;game.&lt;/p&gt;
&lt;div class="prose-full-illustration"&gt;
&lt;img src="https://eev.ee/media/cheezball/06d-chamber-of-secrets-text-grid.png" alt="Harry Potter and the Chamber of Secrets, also scaled up with a grid, showing its text isn't fixed to the grid"&gt;
&lt;/div&gt;

&lt;p&gt;&lt;em&gt;Hot damn.&lt;/em&gt;  That is a &lt;em&gt;ton&lt;/em&gt; of text in a relatively small amount of space!  And it doesn&amp;#8217;t fit the grid!  How did they do&amp;nbsp;that?&lt;/p&gt;
&lt;p&gt;The answer is…  exactly how you&amp;#8217;d&amp;nbsp;think!&lt;/p&gt;
&lt;div class="prose-full-illustration"&gt;
&lt;img src="https://eev.ee/media/cheezball/06e-chamber-of-secrets-tiles.png" alt="Tile display for the above screenshot, showing that the text is simply written across consecutive tiles"&gt;
&lt;/div&gt;

&lt;p&gt;With a fixed-width font like in Pokémon and Zelda games, the entire character set is stored in &lt;span class="caps"&gt;VRAM&lt;/span&gt;, and text is drawn by drawing a string of characters.  With a variable-width font like in Harry Potter, a block of &lt;span class="caps"&gt;VRAM&lt;/span&gt; is reserved for text, and text is drawn &lt;em&gt;into those chars, in software&lt;/em&gt;.  Essentially, some chars are used like a canvas and have text rendered to them on the fly.  The contents of the background layer might look like this in the two&amp;nbsp;cases:&lt;/p&gt;
&lt;div class="prose-full-illustration"&gt;
&lt;img src="https://eev.ee/media/cheezball/06f-fixed-variable-font-comparison.png" alt="Illustration of fixed width versus variable width text"&gt;
&lt;/div&gt;

&lt;p&gt;Some pros of this&amp;nbsp;approach:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Since the number of chars required is constant and the font is never loaded directly into char memory, the font can have arbitrarily many glyphs in it.  Multiple fonts could be used at the same time, even.  (Of course, if you have more than 256 glyphs, you&amp;#8217;ll have to come up with a multi-byte encoding for actually storing the&amp;nbsp;text…)&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;A &lt;em&gt;lot&lt;/em&gt; more text can fit in one line while still remaining&amp;nbsp;readable.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;It has the potential to look &lt;em&gt;very&lt;/em&gt; cool.  I definitely want to squeeze every last drop of fancy-pants graphical stuff that I can from this&amp;nbsp;hardware.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;And,&amp;nbsp;cons:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;It&amp;#8217;s definitely more complicated!  But I only have to write the code once, and since the game won&amp;#8217;t be doing anything &lt;em&gt;but&lt;/em&gt; drawing dialogue while the box is up, I don&amp;#8217;t think I&amp;#8217;ll be in danger of blowing my &lt;span class="caps"&gt;CPU&lt;/span&gt;&amp;nbsp;budget.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Colored text becomes a &lt;em&gt;bit&lt;/em&gt; trickier.  But still possible, so, we can worry about that&amp;nbsp;later.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Fixed text that &lt;em&gt;doesn&amp;#8217;t&lt;/em&gt; scroll, like on menus and whatnot, will be something of a problem — this whole idea relies on amortizing the text rendering across multiple frames.  On the other hand, this game shouldn&amp;#8217;t have &lt;em&gt;too&lt;/em&gt; much of that, and this sounds like a good excuse to hand-draw fixed text (which can then be much more visually interesting).  &lt;em&gt;At worst&lt;/em&gt;, I could just render the fixed text ahead of&amp;nbsp;time.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Well, I&amp;#8217;m sold.  Let&amp;#8217;s give it a&amp;nbsp;shot.&lt;/p&gt;
&lt;h2 id="first-pass"&gt;&lt;a class="toclink" href="#first-pass"&gt;First&amp;nbsp;pass&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Well, I want to do something on a button press, so, let&amp;#8217;s do&amp;nbsp;that.&lt;/p&gt;
&lt;p&gt;A lot of games (older ones especially) have bugs from switching &amp;#8220;modes&amp;#8221; in the same frame that something else happens.  I don&amp;#8217;t entirely understand why that&amp;#8217;s so common and should probably ask some speedrunners, but I &lt;em&gt;should&lt;/em&gt; be fine if I do mode-switching first thing in the frame, and then start over a new frame when switching back to &amp;#8220;world&amp;#8221; mode.  Right?&amp;nbsp;Sure.&lt;/p&gt;
&lt;table class="highlighttable"&gt;&lt;tr&gt;&lt;td class="linenos"&gt;&lt;div class="linenodiv"&gt;&lt;pre&gt;&lt;span class="normal"&gt; 1&lt;/span&gt;
&lt;span class="normal"&gt; 2&lt;/span&gt;
&lt;span class="normal"&gt; 3&lt;/span&gt;
&lt;span class="normal"&gt; 4&lt;/span&gt;
&lt;span class="normal"&gt; 5&lt;/span&gt;
&lt;span class="normal"&gt; 6&lt;/span&gt;
&lt;span class="normal"&gt; 7&lt;/span&gt;
&lt;span class="normal"&gt; 8&lt;/span&gt;
&lt;span class="normal"&gt; 9&lt;/span&gt;
&lt;span class="normal"&gt;10&lt;/span&gt;
&lt;span class="normal"&gt;11&lt;/span&gt;
&lt;span class="normal"&gt;12&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/td&gt;&lt;td class="code"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;    &lt;span class="c1"&gt;; ... button reading code in main loop ...&lt;/span&gt;
    &lt;span class="ow"&gt;bit&lt;/span&gt; &lt;span class="nv"&gt;BUTTON_A&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;a&lt;/span&gt;
    &lt;span class="ow"&gt;jp&lt;/span&gt; &lt;span class="nv"&gt;nz&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="err"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;do_show_dialogue&lt;/span&gt;

    &lt;span class="c1"&gt;; ... main loop ...&lt;/span&gt;

    &lt;span class="c1"&gt;; Loop again when done&lt;/span&gt;
    &lt;span class="ow"&gt;jp&lt;/span&gt; &lt;span class="nv"&gt;vblank_loop&lt;/span&gt;

&lt;span class="nf"&gt;.do_show_dialogue:&lt;/span&gt;
    &lt;span class="ow"&gt;call&lt;/span&gt; &lt;span class="nv"&gt;show_dialogue&lt;/span&gt;
    &lt;span class="ow"&gt;jp&lt;/span&gt; &lt;span class="nv"&gt;vblank_loop&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;p&gt;The extra level of indirection added by &lt;code&gt;.do_show_dialogue&lt;/code&gt; is just so the dialogue code itself isn&amp;#8217;t responsible for knowing where the main loop point is; it can just &lt;code&gt;ret&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Now to actually do something.  This is a first pass, so I want to do as little as possible.  I&amp;#8217;ll definitely need a palette for drawing the text — and here I&amp;#8217;m cutting into my 8-palette budget again, which I don&amp;#8217;t love, but I can figure that out later.  (Maybe with some shenanigans involving changing the palettes mid-redraw,&amp;nbsp;even.)&lt;/p&gt;
&lt;table class="highlighttable"&gt;&lt;tr&gt;&lt;td class="linenos"&gt;&lt;div class="linenodiv"&gt;&lt;pre&gt;&lt;span class="normal"&gt; 1&lt;/span&gt;
&lt;span class="normal"&gt; 2&lt;/span&gt;
&lt;span class="normal"&gt; 3&lt;/span&gt;
&lt;span class="normal"&gt; 4&lt;/span&gt;
&lt;span class="normal"&gt; 5&lt;/span&gt;
&lt;span class="normal"&gt; 6&lt;/span&gt;
&lt;span class="normal"&gt; 7&lt;/span&gt;
&lt;span class="normal"&gt; 8&lt;/span&gt;
&lt;span class="normal"&gt; 9&lt;/span&gt;
&lt;span class="normal"&gt;10&lt;/span&gt;
&lt;span class="normal"&gt;11&lt;/span&gt;
&lt;span class="normal"&gt;12&lt;/span&gt;
&lt;span class="normal"&gt;13&lt;/span&gt;
&lt;span class="normal"&gt;14&lt;/span&gt;
&lt;span class="normal"&gt;15&lt;/span&gt;
&lt;span class="normal"&gt;16&lt;/span&gt;
&lt;span class="normal"&gt;17&lt;/span&gt;
&lt;span class="normal"&gt;18&lt;/span&gt;
&lt;span class="normal"&gt;19&lt;/span&gt;
&lt;span class="normal"&gt;20&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/td&gt;&lt;td class="code"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="nf"&gt;PALETTE_TEXT:&lt;/span&gt;
    &lt;span class="c1"&gt;; Black background, white text...  then gray shadow, maybe?&lt;/span&gt;
    &lt;span class="err"&gt;dcolor $000000&lt;/span&gt;
    &lt;span class="err"&gt;dcolor $ffffff&lt;/span&gt;
    &lt;span class="err"&gt;dcolor $999999&lt;/span&gt;
    &lt;span class="err"&gt;dcolor $666666&lt;/span&gt;

&lt;span class="nf"&gt;show_dialogue:&lt;/span&gt;
    &lt;span class="c1"&gt;; Have to disable the LCD to do video work.  Later I can do&lt;/span&gt;
    &lt;span class="c1"&gt;; a less jarring transition&lt;/span&gt;
    &lt;span class="err"&gt;DisableLCD&lt;/span&gt;

    &lt;span class="c1"&gt;; Copy the palette into slot 7 for now&lt;/span&gt;
    &lt;span class="ow"&gt;ld&lt;/span&gt; &lt;span class="nb"&gt;a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mb"&gt;%10111000&lt;/span&gt;
    &lt;span class="ow"&gt;ld&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;rBCPS&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="nb"&gt;a&lt;/span&gt;
    &lt;span class="ow"&gt;ld&lt;/span&gt; &lt;span class="nb"&gt;hl&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;PALETTE_TEXT&lt;/span&gt;
    &lt;span class="k"&gt;REPT 8&lt;/span&gt;
    &lt;span class="ow"&gt;ld&lt;/span&gt; &lt;span class="nb"&gt;a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;hl&lt;/span&gt;&lt;span class="p"&gt;+]&lt;/span&gt;
    &lt;span class="ow"&gt;ld&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;rBCPD&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="nb"&gt;a&lt;/span&gt;
    &lt;span class="k"&gt;ENDR&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;p&gt;I also know &lt;em&gt;ahead of time&lt;/em&gt; what chars will need to go where on the screen, so I can fill them in&amp;nbsp;now.&lt;/p&gt;
&lt;p&gt;Note that I really ought to blank them all out, especially since they may still contain text from some previous dialogue, but I don&amp;#8217;t do that&amp;nbsp;yet.&lt;/p&gt;
&lt;p&gt;An obvious question is: &lt;em&gt;which tiles&lt;/em&gt;?  I think I said before that with 512 chars available, and ¾ of those still being enough to cover the entire screen in unique chars, I&amp;#8217;m okay with dedicating a quarter of my space to &lt;span class="caps"&gt;UI&lt;/span&gt; stuff, including text.  To keep that stuff &amp;#8220;out of the way&amp;#8221;, I&amp;#8217;ll put them at the &amp;#8220;end&amp;#8221; — bank 1, starting from&amp;nbsp;$80.&lt;/p&gt;
&lt;p&gt;I&amp;#8217;m thinking of having characters be about the same proportions as in the Oracle games.  Those games use 5 rows of tiles, like&amp;nbsp;this:&lt;/p&gt;
&lt;table class="highlighttable"&gt;&lt;tr&gt;&lt;td class="linenos"&gt;&lt;div class="linenodiv"&gt;&lt;pre&gt;&lt;span class="normal"&gt;1&lt;/span&gt;
&lt;span class="normal"&gt;2&lt;/span&gt;
&lt;span class="normal"&gt;3&lt;/span&gt;
&lt;span class="normal"&gt;4&lt;/span&gt;
&lt;span class="normal"&gt;5&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/td&gt;&lt;td class="code"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;top of line 1
bottom of line 1
top of line 2
bottom of line 2
blank
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;p&gt;Since the font is aligned to the bottom and only peeks a little bit into the top char, the very top row is &lt;em&gt;mostly&lt;/em&gt; blank, and that serves as a top margin.  The bottom row is explicitly blank for a bottom margin that&amp;#8217;s nearly the same size.  The space at the top of line 2 then works as line&amp;nbsp;spacing.&lt;/p&gt;
&lt;p&gt;I&amp;#8217;m not fixed to the grid, so I can control line spacing a little more explicitly.  But I&amp;#8217;ll get to that later and do something really simple for now, where $ff is a blank&amp;nbsp;tile:&lt;/p&gt;
&lt;table class="highlighttable"&gt;&lt;tr&gt;&lt;td class="linenos"&gt;&lt;div class="linenodiv"&gt;&lt;pre&gt;&lt;span class="normal"&gt;1&lt;/span&gt;
&lt;span class="normal"&gt;2&lt;/span&gt;
&lt;span class="normal"&gt;3&lt;/span&gt;
&lt;span class="normal"&gt;4&lt;/span&gt;
&lt;span class="normal"&gt;5&lt;/span&gt;
&lt;span class="normal"&gt;6&lt;/span&gt;
&lt;span class="normal"&gt;7&lt;/span&gt;
&lt;span class="normal"&gt;8&lt;/span&gt;
&lt;span class="normal"&gt;9&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/td&gt;&lt;td class="code"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="nb"&gt;+--+--+--+--+--+--+--+--+--+--+--+--+--+---+&lt;/span&gt;&lt;span class="c"&gt;&lt;/span&gt;
&lt;span class="c"&gt;|ff|ff|ff|ff|ff|ff|ff|ff|ff|ff|ff|ff|ff|&lt;/span&gt;&lt;span class="nt"&gt;...&lt;/span&gt;&lt;span class="c"&gt;|&lt;/span&gt;
&lt;span class="nb"&gt;+--+--+--+--+--+--+--+--+--+--+--+--+--+---+&lt;/span&gt;&lt;span class="c"&gt;&lt;/span&gt;
&lt;span class="c"&gt;|ff|80|82|84|86|88|8a|8c|8e|90|92|94|96|&lt;/span&gt;&lt;span class="nt"&gt;...&lt;/span&gt;&lt;span class="c"&gt;|&lt;/span&gt;
&lt;span class="nb"&gt;+--+--+--+--+--+--+--+--+--+--+--+--+--+---+&lt;/span&gt;&lt;span class="c"&gt;&lt;/span&gt;
&lt;span class="c"&gt;|ff|81|83|85|87|89|8b|8d|8f|91|93|95|97|&lt;/span&gt;&lt;span class="nt"&gt;...&lt;/span&gt;&lt;span class="c"&gt;|&lt;/span&gt;
&lt;span class="nb"&gt;+--+--+--+--+--+--+--+--+--+--+--+--+--+---+&lt;/span&gt;&lt;span class="c"&gt;&lt;/span&gt;
&lt;span class="c"&gt;|ff|ff|ff|ff|ff|ff|ff|ff|ff|ff|ff|ff|ff|&lt;/span&gt;&lt;span class="nt"&gt;...&lt;/span&gt;&lt;span class="c"&gt;|&lt;/span&gt;
&lt;span class="nb"&gt;+--+--+--+--+--+--+--+--+--+--+--+--+--+---+&lt;/span&gt;&lt;span class="c"&gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;p&gt;This gives me a canvas for drawing a single line of text.  The staggering means that the first letter will draw to adjacent chars $80 and $81, rather than distant cousins like $80 and&amp;nbsp;$a0.&lt;/p&gt;
&lt;p&gt;You may notice that the below code updates chars across the entire width of the &lt;em&gt;grid&lt;/em&gt;, not merely the screen.  There&amp;#8217;s not really any good reason for&amp;nbsp;that.&lt;/p&gt;
&lt;table class="highlighttable"&gt;&lt;tr&gt;&lt;td class="linenos"&gt;&lt;div class="linenodiv"&gt;&lt;pre&gt;&lt;span class="normal"&gt; 1&lt;/span&gt;
&lt;span class="normal"&gt; 2&lt;/span&gt;
&lt;span class="normal"&gt; 3&lt;/span&gt;
&lt;span class="normal"&gt; 4&lt;/span&gt;
&lt;span class="normal"&gt; 5&lt;/span&gt;
&lt;span class="normal"&gt; 6&lt;/span&gt;
&lt;span class="normal"&gt; 7&lt;/span&gt;
&lt;span class="normal"&gt; 8&lt;/span&gt;
&lt;span class="normal"&gt; 9&lt;/span&gt;
&lt;span class="normal"&gt;10&lt;/span&gt;
&lt;span class="normal"&gt;11&lt;/span&gt;
&lt;span class="normal"&gt;12&lt;/span&gt;
&lt;span class="normal"&gt;13&lt;/span&gt;
&lt;span class="normal"&gt;14&lt;/span&gt;
&lt;span class="normal"&gt;15&lt;/span&gt;
&lt;span class="normal"&gt;16&lt;/span&gt;
&lt;span class="normal"&gt;17&lt;/span&gt;
&lt;span class="normal"&gt;18&lt;/span&gt;
&lt;span class="normal"&gt;19&lt;/span&gt;
&lt;span class="normal"&gt;20&lt;/span&gt;
&lt;span class="normal"&gt;21&lt;/span&gt;
&lt;span class="normal"&gt;22&lt;/span&gt;
&lt;span class="normal"&gt;23&lt;/span&gt;
&lt;span class="normal"&gt;24&lt;/span&gt;
&lt;span class="normal"&gt;25&lt;/span&gt;
&lt;span class="normal"&gt;26&lt;/span&gt;
&lt;span class="normal"&gt;27&lt;/span&gt;
&lt;span class="normal"&gt;28&lt;/span&gt;
&lt;span class="normal"&gt;29&lt;/span&gt;
&lt;span class="normal"&gt;30&lt;/span&gt;
&lt;span class="normal"&gt;31&lt;/span&gt;
&lt;span class="normal"&gt;32&lt;/span&gt;
&lt;span class="normal"&gt;33&lt;/span&gt;
&lt;span class="normal"&gt;34&lt;/span&gt;
&lt;span class="normal"&gt;35&lt;/span&gt;
&lt;span class="normal"&gt;36&lt;/span&gt;
&lt;span class="normal"&gt;37&lt;/span&gt;
&lt;span class="normal"&gt;38&lt;/span&gt;
&lt;span class="normal"&gt;39&lt;/span&gt;
&lt;span class="normal"&gt;40&lt;/span&gt;
&lt;span class="normal"&gt;41&lt;/span&gt;
&lt;span class="normal"&gt;42&lt;/span&gt;
&lt;span class="normal"&gt;43&lt;/span&gt;
&lt;span class="normal"&gt;44&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/td&gt;&lt;td class="code"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;    &lt;span class="c1"&gt;; Fill text rows with tiles (blank border, custom tiles)&lt;/span&gt;
    &lt;span class="c1"&gt;; The screen has 144/8 = 18 rows, so skip the first 14 rows&lt;/span&gt;
    &lt;span class="ow"&gt;ld&lt;/span&gt; &lt;span class="nb"&gt;hl&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mh"&gt;$9800&lt;/span&gt; &lt;span class="p"&gt;+&lt;/span&gt; &lt;span class="nv"&gt;32&lt;/span&gt; &lt;span class="err"&gt;*&lt;/span&gt; &lt;span class="nv"&gt;14&lt;/span&gt;
    &lt;span class="c1"&gt;; Top row, all tile 255&lt;/span&gt;
    &lt;span class="ow"&gt;ld&lt;/span&gt; &lt;span class="nb"&gt;a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;255&lt;/span&gt;
    &lt;span class="ow"&gt;ld&lt;/span&gt; &lt;span class="nb"&gt;c&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;32&lt;/span&gt;
&lt;span class="nf"&gt;.loop1:&lt;/span&gt;
    &lt;span class="ow"&gt;ld&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;hl&lt;/span&gt;&lt;span class="p"&gt;+],&lt;/span&gt; &lt;span class="nb"&gt;a&lt;/span&gt;
    &lt;span class="ow"&gt;dec&lt;/span&gt; &lt;span class="nb"&gt;c&lt;/span&gt;
    &lt;span class="ow"&gt;jr&lt;/span&gt; &lt;span class="nv"&gt;nz&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="err"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;loop1&lt;/span&gt;

    &lt;span class="c1"&gt;; Text row 1: 255 on the edges, then middle goes 128, 130, ...&lt;/span&gt;
    &lt;span class="ow"&gt;ld&lt;/span&gt; &lt;span class="nb"&gt;a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;255&lt;/span&gt;
    &lt;span class="ow"&gt;ld&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;hl&lt;/span&gt;&lt;span class="p"&gt;+],&lt;/span&gt; &lt;span class="nb"&gt;a&lt;/span&gt;
    &lt;span class="ow"&gt;ld&lt;/span&gt; &lt;span class="nb"&gt;a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;128&lt;/span&gt;
    &lt;span class="ow"&gt;ld&lt;/span&gt; &lt;span class="nb"&gt;c&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;30&lt;/span&gt;
&lt;span class="nf"&gt;.loop2:&lt;/span&gt;
    &lt;span class="ow"&gt;ld&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;hl&lt;/span&gt;&lt;span class="p"&gt;+],&lt;/span&gt; &lt;span class="nb"&gt;a&lt;/span&gt;
    &lt;span class="ow"&gt;add&lt;/span&gt; &lt;span class="nb"&gt;a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;2&lt;/span&gt;
    &lt;span class="ow"&gt;dec&lt;/span&gt; &lt;span class="nb"&gt;c&lt;/span&gt;
    &lt;span class="ow"&gt;jr&lt;/span&gt; &lt;span class="nv"&gt;nz&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="err"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;loop2&lt;/span&gt;
    &lt;span class="ow"&gt;ld&lt;/span&gt; &lt;span class="nb"&gt;a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;255&lt;/span&gt;
    &lt;span class="ow"&gt;ld&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;hl&lt;/span&gt;&lt;span class="p"&gt;+],&lt;/span&gt; &lt;span class="nb"&gt;a&lt;/span&gt;

    &lt;span class="c1"&gt;; Text row 2: same as above, but middle is 129, 131, ...&lt;/span&gt;
    &lt;span class="ow"&gt;ld&lt;/span&gt; &lt;span class="nb"&gt;a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;255&lt;/span&gt;
    &lt;span class="ow"&gt;ld&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;hl&lt;/span&gt;&lt;span class="p"&gt;+],&lt;/span&gt; &lt;span class="nb"&gt;a&lt;/span&gt;
    &lt;span class="ow"&gt;ld&lt;/span&gt; &lt;span class="nb"&gt;a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;129&lt;/span&gt;
    &lt;span class="ow"&gt;ld&lt;/span&gt; &lt;span class="nb"&gt;c&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;30&lt;/span&gt;
&lt;span class="nf"&gt;.loop3:&lt;/span&gt;
    &lt;span class="ow"&gt;ld&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;hl&lt;/span&gt;&lt;span class="p"&gt;+],&lt;/span&gt; &lt;span class="nb"&gt;a&lt;/span&gt;
    &lt;span class="ow"&gt;add&lt;/span&gt; &lt;span class="nb"&gt;a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;2&lt;/span&gt;
    &lt;span class="ow"&gt;dec&lt;/span&gt; &lt;span class="nb"&gt;c&lt;/span&gt;
    &lt;span class="ow"&gt;jr&lt;/span&gt; &lt;span class="nv"&gt;nz&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="err"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;loop3&lt;/span&gt;
    &lt;span class="ow"&gt;ld&lt;/span&gt; &lt;span class="nb"&gt;a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;255&lt;/span&gt;
    &lt;span class="ow"&gt;ld&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;hl&lt;/span&gt;&lt;span class="p"&gt;+],&lt;/span&gt; &lt;span class="nb"&gt;a&lt;/span&gt;

    &lt;span class="c1"&gt;; Bottom row, all tile 255&lt;/span&gt;
    &lt;span class="ow"&gt;ld&lt;/span&gt; &lt;span class="nb"&gt;a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;255&lt;/span&gt;
    &lt;span class="ow"&gt;ld&lt;/span&gt; &lt;span class="nb"&gt;c&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;32&lt;/span&gt;
&lt;span class="nf"&gt;.loop4:&lt;/span&gt;
    &lt;span class="ow"&gt;ld&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;hl&lt;/span&gt;&lt;span class="p"&gt;+],&lt;/span&gt; &lt;span class="nb"&gt;a&lt;/span&gt;
    &lt;span class="ow"&gt;dec&lt;/span&gt; &lt;span class="nb"&gt;c&lt;/span&gt;
    &lt;span class="ow"&gt;jr&lt;/span&gt; &lt;span class="nv"&gt;nz&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="err"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;loop4&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;p&gt;Now I need to repeat all of that, but in bank 1, to specify the char bank (1) and palette (7) for the corresponding tiles.  Those are the same for the entire dialogue box, though, so this part is&amp;nbsp;easier.&lt;/p&gt;
&lt;table class="highlighttable"&gt;&lt;tr&gt;&lt;td class="linenos"&gt;&lt;div class="linenodiv"&gt;&lt;pre&gt;&lt;span class="normal"&gt; 1&lt;/span&gt;
&lt;span class="normal"&gt; 2&lt;/span&gt;
&lt;span class="normal"&gt; 3&lt;/span&gt;
&lt;span class="normal"&gt; 4&lt;/span&gt;
&lt;span class="normal"&gt; 5&lt;/span&gt;
&lt;span class="normal"&gt; 6&lt;/span&gt;
&lt;span class="normal"&gt; 7&lt;/span&gt;
&lt;span class="normal"&gt; 8&lt;/span&gt;
&lt;span class="normal"&gt; 9&lt;/span&gt;
&lt;span class="normal"&gt;10&lt;/span&gt;
&lt;span class="normal"&gt;11&lt;/span&gt;
&lt;span class="normal"&gt;12&lt;/span&gt;
&lt;span class="normal"&gt;13&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/td&gt;&lt;td class="code"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;    &lt;span class="c1"&gt;; Switch to VRAM bank 1&lt;/span&gt;
    &lt;span class="ow"&gt;ld&lt;/span&gt; &lt;span class="nb"&gt;a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;1&lt;/span&gt;
    &lt;span class="ow"&gt;ldh&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;rVBK&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="nb"&gt;a&lt;/span&gt;

    &lt;span class="ow"&gt;ld&lt;/span&gt; &lt;span class="nb"&gt;a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mb"&gt;%00001111&lt;/span&gt;  &lt;span class="c1"&gt;; bank 1, palette 7&lt;/span&gt;
    &lt;span class="ow"&gt;ld&lt;/span&gt; &lt;span class="nb"&gt;hl&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mh"&gt;$9800&lt;/span&gt; &lt;span class="p"&gt;+&lt;/span&gt; &lt;span class="nv"&gt;32&lt;/span&gt; &lt;span class="err"&gt;*&lt;/span&gt; &lt;span class="nv"&gt;14&lt;/span&gt;
    &lt;span class="ow"&gt;ld&lt;/span&gt; &lt;span class="nb"&gt;c&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;32&lt;/span&gt; &lt;span class="err"&gt;*&lt;/span&gt; &lt;span class="nv"&gt;4&lt;/span&gt;  &lt;span class="c1"&gt;; 4 rows&lt;/span&gt;
&lt;span class="nf"&gt;.loop5:&lt;/span&gt;
    &lt;span class="ow"&gt;ld&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;hl&lt;/span&gt;&lt;span class="p"&gt;+],&lt;/span&gt; &lt;span class="nb"&gt;a&lt;/span&gt;
    &lt;span class="ow"&gt;dec&lt;/span&gt; &lt;span class="nb"&gt;c&lt;/span&gt;
    &lt;span class="ow"&gt;jr&lt;/span&gt; &lt;span class="nv"&gt;nz&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="err"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;loop5&lt;/span&gt;

    &lt;span class="err"&gt;EnableLCD&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;p&gt;Time to get some real work done.  Which raises the question: how do I actually do&amp;nbsp;this?&lt;/p&gt;
&lt;p&gt;If you recall, each 8-pixel row of a char is stored in two bytes.  The two-bit palette index for each pixel is split across the corresponding bit in each byte.  If the leftmost pixel is palette index 01, then bit 7 in the first byte will be 0, and bit 7 in the second byte will be&amp;nbsp;1.&lt;/p&gt;
&lt;p&gt;Now, a blank char is all zeroes.  To write a (left-aligned) glyph into a blank char, all I need to do is…  well, I could overwrite it, but I could just as well &lt;span class="caps"&gt;OR&lt;/span&gt; it.  To write a &lt;em&gt;second&lt;/em&gt; glyph into the unused space, all I need to do is &lt;em&gt;shift it right&lt;/em&gt; by the width of the space used so far, and &lt;span class="caps"&gt;OR&lt;/span&gt; it on top.  The unusual split layout of the palette data is actually handy here, because it means the size of the shift matches the number of pixels, &lt;em&gt;and&lt;/em&gt; I don&amp;#8217;t have to worry about&amp;nbsp;overflow.&lt;/p&gt;
&lt;table class="highlighttable"&gt;&lt;tr&gt;&lt;td class="linenos"&gt;&lt;div class="linenodiv"&gt;&lt;pre&gt;&lt;span class="normal"&gt; 1&lt;/span&gt;
&lt;span class="normal"&gt; 2&lt;/span&gt;
&lt;span class="normal"&gt; 3&lt;/span&gt;
&lt;span class="normal"&gt; 4&lt;/span&gt;
&lt;span class="normal"&gt; 5&lt;/span&gt;
&lt;span class="normal"&gt; 6&lt;/span&gt;
&lt;span class="normal"&gt; 7&lt;/span&gt;
&lt;span class="normal"&gt; 8&lt;/span&gt;
&lt;span class="normal"&gt; 9&lt;/span&gt;
&lt;span class="normal"&gt;10&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/td&gt;&lt;td class="code"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="mf"&gt;0&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;0&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;0&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;0&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;0&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;0&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;0&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;0&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="o"&gt;&amp;lt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;blank&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;glyph&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;

&lt;span class="mf"&gt;1&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;1&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;1&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;1&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;0&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;0&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;0&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;0&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="o"&gt;&amp;lt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;some&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;byte&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;from&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;the&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;first&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;glyph&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="err"&gt;↓&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;↓&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;↓&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;↓&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;↓&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;↓&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;↓&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;↓&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="mf"&gt;1&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;1&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;1&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;1&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;0&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;0&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;0&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;0&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="o"&gt;&amp;lt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ow"&gt;OR&lt;/span&gt;&lt;span class="n"&gt;ed&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kr"&gt;toget&lt;/span&gt;&lt;span class="n"&gt;her&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kr"&gt;to&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;display&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;first&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;character&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;

&lt;span class="w"&gt;          &lt;/span&gt;&lt;span class="mf"&gt;1&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;1&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;1&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;1&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;0&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;0&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;0&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;0&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="o"&gt;&amp;lt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;some&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;byte&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;from&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;the&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;second&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;glyph&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;                              &lt;/span&gt;&lt;span class="n"&gt;shifted&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;by&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;4&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;plus&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;kerning&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;pixel&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="err"&gt;↓&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;↓&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;↓&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;↓&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;↓&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;↓&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;↓&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;↓&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="mf"&gt;1&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;1&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;1&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;1&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;0&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;1&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;1&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;1&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="o"&gt;&amp;lt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ow"&gt;OR&lt;/span&gt;&lt;span class="n"&gt;ed&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kr"&gt;toget&lt;/span&gt;&lt;span class="n"&gt;her&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kr"&gt;to&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;display&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;first&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;two&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;characters&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;p&gt;The obvious question is, well, what happens to the bits from the second character that didn&amp;#8217;t fit?  I&amp;#8217;ll worry about that a bit&amp;nbsp;later.&lt;/p&gt;
&lt;p&gt;Oh, and finally, I&amp;#8217;ll need &lt;em&gt;a font&lt;/em&gt;, plus some text to display.  This is still just a proof of concept, so I&amp;#8217;ll add in a couple glyphs by&amp;nbsp;hand.&lt;/p&gt;
&lt;table class="highlighttable"&gt;&lt;tr&gt;&lt;td class="linenos"&gt;&lt;div class="linenodiv"&gt;&lt;pre&gt;&lt;span class="normal"&gt; 1&lt;/span&gt;
&lt;span class="normal"&gt; 2&lt;/span&gt;
&lt;span class="normal"&gt; 3&lt;/span&gt;
&lt;span class="normal"&gt; 4&lt;/span&gt;
&lt;span class="normal"&gt; 5&lt;/span&gt;
&lt;span class="normal"&gt; 6&lt;/span&gt;
&lt;span class="normal"&gt; 7&lt;/span&gt;
&lt;span class="normal"&gt; 8&lt;/span&gt;
&lt;span class="normal"&gt; 9&lt;/span&gt;
&lt;span class="normal"&gt;10&lt;/span&gt;
&lt;span class="normal"&gt;11&lt;/span&gt;
&lt;span class="normal"&gt;12&lt;/span&gt;
&lt;span class="normal"&gt;13&lt;/span&gt;
&lt;span class="normal"&gt;14&lt;/span&gt;
&lt;span class="normal"&gt;15&lt;/span&gt;
&lt;span class="normal"&gt;16&lt;/span&gt;
&lt;span class="normal"&gt;17&lt;/span&gt;
&lt;span class="normal"&gt;18&lt;/span&gt;
&lt;span class="normal"&gt;19&lt;/span&gt;
&lt;span class="normal"&gt;20&lt;/span&gt;
&lt;span class="normal"&gt;21&lt;/span&gt;
&lt;span class="normal"&gt;22&lt;/span&gt;
&lt;span class="normal"&gt;23&lt;/span&gt;
&lt;span class="normal"&gt;24&lt;/span&gt;
&lt;span class="normal"&gt;25&lt;/span&gt;
&lt;span class="normal"&gt;26&lt;/span&gt;
&lt;span class="normal"&gt;27&lt;/span&gt;
&lt;span class="normal"&gt;28&lt;/span&gt;
&lt;span class="normal"&gt;29&lt;/span&gt;
&lt;span class="normal"&gt;30&lt;/span&gt;
&lt;span class="normal"&gt;31&lt;/span&gt;
&lt;span class="normal"&gt;32&lt;/span&gt;
&lt;span class="normal"&gt;33&lt;/span&gt;
&lt;span class="normal"&gt;34&lt;/span&gt;
&lt;span class="normal"&gt;35&lt;/span&gt;
&lt;span class="normal"&gt;36&lt;/span&gt;
&lt;span class="normal"&gt;37&lt;/span&gt;
&lt;span class="normal"&gt;38&lt;/span&gt;
&lt;span class="normal"&gt;39&lt;/span&gt;
&lt;span class="normal"&gt;40&lt;/span&gt;
&lt;span class="normal"&gt;41&lt;/span&gt;
&lt;span class="normal"&gt;42&lt;/span&gt;
&lt;span class="normal"&gt;43&lt;/span&gt;
&lt;span class="normal"&gt;44&lt;/span&gt;
&lt;span class="normal"&gt;45&lt;/span&gt;
&lt;span class="normal"&gt;46&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/td&gt;&lt;td class="code"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="c1"&gt;; somewhere in ROM&lt;/span&gt;
&lt;span class="nf"&gt;font:&lt;/span&gt;
&lt;span class="c1"&gt;; A&lt;/span&gt;
    &lt;span class="c1"&gt;; First byte indicates the width of the glyph, which I need&lt;/span&gt;
    &lt;span class="c1"&gt;; to know because the width varies!&lt;/span&gt;
    &lt;span class="k"&gt;db 6&lt;/span&gt;
    &lt;span class="k"&gt;dw `00000000&lt;/span&gt;
    &lt;span class="k"&gt;dw `00000000&lt;/span&gt;
    &lt;span class="k"&gt;dw `01110000&lt;/span&gt;
    &lt;span class="k"&gt;dw `10001000&lt;/span&gt;
    &lt;span class="k"&gt;dw `10001000&lt;/span&gt;
    &lt;span class="k"&gt;dw `10001000&lt;/span&gt;
    &lt;span class="k"&gt;dw `11111000&lt;/span&gt;
    &lt;span class="k"&gt;dw `10001000&lt;/span&gt;
    &lt;span class="k"&gt;dw `10001000&lt;/span&gt;
    &lt;span class="k"&gt;dw `10001000&lt;/span&gt;
    &lt;span class="k"&gt;dw `10001000&lt;/span&gt;
    &lt;span class="k"&gt;dw `00000000&lt;/span&gt;
    &lt;span class="k"&gt;dw `00000000&lt;/span&gt;
    &lt;span class="k"&gt;dw `00000000&lt;/span&gt;
    &lt;span class="k"&gt;dw `00000000&lt;/span&gt;
    &lt;span class="k"&gt;dw `00000000&lt;/span&gt;
&lt;span class="c1"&gt;; B&lt;/span&gt;
    &lt;span class="k"&gt;db 6&lt;/span&gt;
    &lt;span class="k"&gt;dw `00000000&lt;/span&gt;
    &lt;span class="k"&gt;dw `00000000&lt;/span&gt;
    &lt;span class="k"&gt;dw `11110000&lt;/span&gt;
    &lt;span class="k"&gt;dw `10001000&lt;/span&gt;
    &lt;span class="k"&gt;dw `10001000&lt;/span&gt;
    &lt;span class="k"&gt;dw `10001000&lt;/span&gt;
    &lt;span class="k"&gt;dw `11110000&lt;/span&gt;
    &lt;span class="k"&gt;dw `10001000&lt;/span&gt;
    &lt;span class="k"&gt;dw `10001000&lt;/span&gt;
    &lt;span class="k"&gt;dw `10001000&lt;/span&gt;
    &lt;span class="k"&gt;dw `11110000&lt;/span&gt;
    &lt;span class="k"&gt;dw `00000000&lt;/span&gt;
    &lt;span class="k"&gt;dw `00000000&lt;/span&gt;
    &lt;span class="k"&gt;dw `00000000&lt;/span&gt;
    &lt;span class="k"&gt;dw `00000000&lt;/span&gt;
    &lt;span class="k"&gt;dw `00000000&lt;/span&gt;

&lt;span class="nf"&gt;text:&lt;/span&gt;
    &lt;span class="c1"&gt;; Shakespeare it ain&amp;#39;t.&lt;/span&gt;
    &lt;span class="c1"&gt;; Need to end with a NUL here so I know where the text&lt;/span&gt;
    &lt;span class="c1"&gt;; ends.  This isn&amp;#39;t C, there&amp;#39;s no automatic termination!&lt;/span&gt;
    &lt;span class="k"&gt;db &amp;quot;ABABAAA&amp;quot;, 0&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;p&gt;And here we&amp;nbsp;go!&lt;/p&gt;
&lt;table class="highlighttable"&gt;&lt;tr&gt;&lt;td class="linenos"&gt;&lt;div class="linenodiv"&gt;&lt;pre&gt;&lt;span class="normal"&gt;  1&lt;/span&gt;
&lt;span class="normal"&gt;  2&lt;/span&gt;
&lt;span class="normal"&gt;  3&lt;/span&gt;
&lt;span class="normal"&gt;  4&lt;/span&gt;
&lt;span class="normal"&gt;  5&lt;/span&gt;
&lt;span class="normal"&gt;  6&lt;/span&gt;
&lt;span class="normal"&gt;  7&lt;/span&gt;
&lt;span class="normal"&gt;  8&lt;/span&gt;
&lt;span class="normal"&gt;  9&lt;/span&gt;
&lt;span class="normal"&gt; 10&lt;/span&gt;
&lt;span class="normal"&gt; 11&lt;/span&gt;
&lt;span class="normal"&gt; 12&lt;/span&gt;
&lt;span class="normal"&gt; 13&lt;/span&gt;
&lt;span class="normal"&gt; 14&lt;/span&gt;
&lt;span class="normal"&gt; 15&lt;/span&gt;
&lt;span class="normal"&gt; 16&lt;/span&gt;
&lt;span class="normal"&gt; 17&lt;/span&gt;
&lt;span class="normal"&gt; 18&lt;/span&gt;
&lt;span class="normal"&gt; 19&lt;/span&gt;
&lt;span class="normal"&gt; 20&lt;/span&gt;
&lt;span class="normal"&gt; 21&lt;/span&gt;
&lt;span class="normal"&gt; 22&lt;/span&gt;
&lt;span class="normal"&gt; 23&lt;/span&gt;
&lt;span class="normal"&gt; 24&lt;/span&gt;
&lt;span class="normal"&gt; 25&lt;/span&gt;
&lt;span class="normal"&gt; 26&lt;/span&gt;
&lt;span class="normal"&gt; 27&lt;/span&gt;
&lt;span class="normal"&gt; 28&lt;/span&gt;
&lt;span class="normal"&gt; 29&lt;/span&gt;
&lt;span class="normal"&gt; 30&lt;/span&gt;
&lt;span class="normal"&gt; 31&lt;/span&gt;
&lt;span class="normal"&gt; 32&lt;/span&gt;
&lt;span class="normal"&gt; 33&lt;/span&gt;
&lt;span class="normal"&gt; 34&lt;/span&gt;
&lt;span class="normal"&gt; 35&lt;/span&gt;
&lt;span class="normal"&gt; 36&lt;/span&gt;
&lt;span class="normal"&gt; 37&lt;/span&gt;
&lt;span class="normal"&gt; 38&lt;/span&gt;
&lt;span class="normal"&gt; 39&lt;/span&gt;
&lt;span class="normal"&gt; 40&lt;/span&gt;
&lt;span class="normal"&gt; 41&lt;/span&gt;
&lt;span class="normal"&gt; 42&lt;/span&gt;
&lt;span class="normal"&gt; 43&lt;/span&gt;
&lt;span class="normal"&gt; 44&lt;/span&gt;
&lt;span class="normal"&gt; 45&lt;/span&gt;
&lt;span class="normal"&gt; 46&lt;/span&gt;
&lt;span class="normal"&gt; 47&lt;/span&gt;
&lt;span class="normal"&gt; 48&lt;/span&gt;
&lt;span class="normal"&gt; 49&lt;/span&gt;
&lt;span class="normal"&gt; 50&lt;/span&gt;
&lt;span class="normal"&gt; 51&lt;/span&gt;
&lt;span class="normal"&gt; 52&lt;/span&gt;
&lt;span class="normal"&gt; 53&lt;/span&gt;
&lt;span class="normal"&gt; 54&lt;/span&gt;
&lt;span class="normal"&gt; 55&lt;/span&gt;
&lt;span class="normal"&gt; 56&lt;/span&gt;
&lt;span class="normal"&gt; 57&lt;/span&gt;
&lt;span class="normal"&gt; 58&lt;/span&gt;
&lt;span class="normal"&gt; 59&lt;/span&gt;
&lt;span class="normal"&gt; 60&lt;/span&gt;
&lt;span class="normal"&gt; 61&lt;/span&gt;
&lt;span class="normal"&gt; 62&lt;/span&gt;
&lt;span class="normal"&gt; 63&lt;/span&gt;
&lt;span class="normal"&gt; 64&lt;/span&gt;
&lt;span class="normal"&gt; 65&lt;/span&gt;
&lt;span class="normal"&gt; 66&lt;/span&gt;
&lt;span class="normal"&gt; 67&lt;/span&gt;
&lt;span class="normal"&gt; 68&lt;/span&gt;
&lt;span class="normal"&gt; 69&lt;/span&gt;
&lt;span class="normal"&gt; 70&lt;/span&gt;
&lt;span class="normal"&gt; 71&lt;/span&gt;
&lt;span class="normal"&gt; 72&lt;/span&gt;
&lt;span class="normal"&gt; 73&lt;/span&gt;
&lt;span class="normal"&gt; 74&lt;/span&gt;
&lt;span class="normal"&gt; 75&lt;/span&gt;
&lt;span class="normal"&gt; 76&lt;/span&gt;
&lt;span class="normal"&gt; 77&lt;/span&gt;
&lt;span class="normal"&gt; 78&lt;/span&gt;
&lt;span class="normal"&gt; 79&lt;/span&gt;
&lt;span class="normal"&gt; 80&lt;/span&gt;
&lt;span class="normal"&gt; 81&lt;/span&gt;
&lt;span class="normal"&gt; 82&lt;/span&gt;
&lt;span class="normal"&gt; 83&lt;/span&gt;
&lt;span class="normal"&gt; 84&lt;/span&gt;
&lt;span class="normal"&gt; 85&lt;/span&gt;
&lt;span class="normal"&gt; 86&lt;/span&gt;
&lt;span class="normal"&gt; 87&lt;/span&gt;
&lt;span class="normal"&gt; 88&lt;/span&gt;
&lt;span class="normal"&gt; 89&lt;/span&gt;
&lt;span class="normal"&gt; 90&lt;/span&gt;
&lt;span class="normal"&gt; 91&lt;/span&gt;
&lt;span class="normal"&gt; 92&lt;/span&gt;
&lt;span class="normal"&gt; 93&lt;/span&gt;
&lt;span class="normal"&gt; 94&lt;/span&gt;
&lt;span class="normal"&gt; 95&lt;/span&gt;
&lt;span class="normal"&gt; 96&lt;/span&gt;
&lt;span class="normal"&gt; 97&lt;/span&gt;
&lt;span class="normal"&gt; 98&lt;/span&gt;
&lt;span class="normal"&gt; 99&lt;/span&gt;
&lt;span class="normal"&gt;100&lt;/span&gt;
&lt;span class="normal"&gt;101&lt;/span&gt;
&lt;span class="normal"&gt;102&lt;/span&gt;
&lt;span class="normal"&gt;103&lt;/span&gt;
&lt;span class="normal"&gt;104&lt;/span&gt;
&lt;span class="normal"&gt;105&lt;/span&gt;
&lt;span class="normal"&gt;106&lt;/span&gt;
&lt;span class="normal"&gt;107&lt;/span&gt;
&lt;span class="normal"&gt;108&lt;/span&gt;
&lt;span class="normal"&gt;109&lt;/span&gt;
&lt;span class="normal"&gt;110&lt;/span&gt;
&lt;span class="normal"&gt;111&lt;/span&gt;
&lt;span class="normal"&gt;112&lt;/span&gt;
&lt;span class="normal"&gt;113&lt;/span&gt;
&lt;span class="normal"&gt;114&lt;/span&gt;
&lt;span class="normal"&gt;115&lt;/span&gt;
&lt;span class="normal"&gt;116&lt;/span&gt;
&lt;span class="normal"&gt;117&lt;/span&gt;
&lt;span class="normal"&gt;118&lt;/span&gt;
&lt;span class="normal"&gt;119&lt;/span&gt;
&lt;span class="normal"&gt;120&lt;/span&gt;
&lt;span class="normal"&gt;121&lt;/span&gt;
&lt;span class="normal"&gt;122&lt;/span&gt;
&lt;span class="normal"&gt;123&lt;/span&gt;
&lt;span class="normal"&gt;124&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/td&gt;&lt;td class="code"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;    &lt;span class="c1"&gt;; ----------------------------------------------------------&lt;/span&gt;
    &lt;span class="c1"&gt;; Setup done!  Real work begins here&lt;/span&gt;
    &lt;span class="c1"&gt;; b: x-offset within current tile&lt;/span&gt;
    &lt;span class="c1"&gt;; de: text cursor + current character tiles&lt;/span&gt;
    &lt;span class="c1"&gt;; hl: current VRAM tile being drawn into&lt;/span&gt;
    &lt;span class="ow"&gt;ld&lt;/span&gt; &lt;span class="nb"&gt;b&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;0&lt;/span&gt;
    &lt;span class="ow"&gt;ld&lt;/span&gt; &lt;span class="nb"&gt;de&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;text&lt;/span&gt;
    &lt;span class="ow"&gt;ld&lt;/span&gt; &lt;span class="nb"&gt;hl&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mh"&gt;$8800&lt;/span&gt;

    &lt;span class="c1"&gt;; This loop waits for the next vblank, then draws a letter.&lt;/span&gt;
    &lt;span class="c1"&gt;; Text thus displays at ~60 characters per second.&lt;/span&gt;
&lt;span class="nf"&gt;.next_letter:&lt;/span&gt;
    &lt;span class="c1"&gt;; This is probably way more LCD disabling than is strictly&lt;/span&gt;
    &lt;span class="c1"&gt;; necessary, but I don&amp;#39;t want to worry about it yet&lt;/span&gt;
    &lt;span class="err"&gt;EnableLCD&lt;/span&gt;
    &lt;span class="ow"&gt;call&lt;/span&gt; &lt;span class="nv"&gt;wait_for_vblank&lt;/span&gt;
    &lt;span class="err"&gt;DisableLCD&lt;/span&gt;

    &lt;span class="ow"&gt;ld&lt;/span&gt; &lt;span class="nb"&gt;a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;de&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;                  &lt;span class="c1"&gt;; get current character&lt;/span&gt;
    &lt;span class="ow"&gt;and&lt;/span&gt; &lt;span class="nb"&gt;a&lt;/span&gt;                       &lt;span class="c1"&gt;; if NUL, we&amp;#39;re done!&lt;/span&gt;
    &lt;span class="ow"&gt;jr&lt;/span&gt; &lt;span class="nv"&gt;z&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="err"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;done&lt;/span&gt;
    &lt;span class="ow"&gt;inc&lt;/span&gt; &lt;span class="nb"&gt;de&lt;/span&gt;                      &lt;span class="c1"&gt;; otherwise, increment&lt;/span&gt;

    &lt;span class="c1"&gt;; Get the glyph from the font, which means computing&lt;/span&gt;
    &lt;span class="c1"&gt;; font + 33 * a.&lt;/span&gt;
    &lt;span class="c1"&gt;; A little register juggling.  hl points to the current&lt;/span&gt;
    &lt;span class="c1"&gt;; char in VRAM being drawn to, but I can only do a 16-bit&lt;/span&gt;
    &lt;span class="c1"&gt;; add into hl.  de I don&amp;#39;t need until the next loop,&lt;/span&gt;
    &lt;span class="c1"&gt;; since I already read from it.  So I&amp;#39;m going to push de&lt;/span&gt;
    &lt;span class="c1"&gt;; AND hl, compute the glyph address in hl, put it in de,&lt;/span&gt;
    &lt;span class="c1"&gt;; then restore hl.&lt;/span&gt;
    &lt;span class="ow"&gt;push&lt;/span&gt; &lt;span class="nb"&gt;de&lt;/span&gt;
    &lt;span class="ow"&gt;push&lt;/span&gt; &lt;span class="nb"&gt;hl&lt;/span&gt;
    &lt;span class="c1"&gt;; The text is written in ASCII, but the glyphs start at 0&lt;/span&gt;
    &lt;span class="ow"&gt;sub&lt;/span&gt; &lt;span class="nb"&gt;a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;65&lt;/span&gt;
    &lt;span class="ow"&gt;ld&lt;/span&gt; &lt;span class="nb"&gt;hl&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;font&lt;/span&gt;
    &lt;span class="ow"&gt;ld&lt;/span&gt; &lt;span class="nb"&gt;de&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;33&lt;/span&gt;                   &lt;span class="c1"&gt;; 1 width byte + 16 * 2 tiles&lt;/span&gt;
    &lt;span class="c1"&gt;; This could probably be faster with long multiplication&lt;/span&gt;
    &lt;span class="ow"&gt;and&lt;/span&gt; &lt;span class="nb"&gt;a&lt;/span&gt;
&lt;span class="nf"&gt;.letter_stride:&lt;/span&gt;
    &lt;span class="ow"&gt;jr&lt;/span&gt; &lt;span class="nv"&gt;z&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="err"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;skip_letter_stride&lt;/span&gt;
    &lt;span class="ow"&gt;add&lt;/span&gt; &lt;span class="nb"&gt;hl&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;de&lt;/span&gt;
    &lt;span class="ow"&gt;dec&lt;/span&gt; &lt;span class="nb"&gt;a&lt;/span&gt;
    &lt;span class="ow"&gt;jr&lt;/span&gt; &lt;span class="err"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;letter_stride&lt;/span&gt;
&lt;span class="nf"&gt;.skip_letter_stride:&lt;/span&gt;
    &lt;span class="c1"&gt;; Move the glyph address into de, and restore hl&lt;/span&gt;
    &lt;span class="ow"&gt;ld&lt;/span&gt; &lt;span class="nb"&gt;d&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;h&lt;/span&gt;
    &lt;span class="ow"&gt;ld&lt;/span&gt; &lt;span class="nb"&gt;e&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;l&lt;/span&gt;
    &lt;span class="ow"&gt;pop&lt;/span&gt; &lt;span class="nb"&gt;hl&lt;/span&gt;

    &lt;span class="c1"&gt;; Read the first byte, which is the character width.  This&lt;/span&gt;
    &lt;span class="c1"&gt;; overwrites the character, but I have the glyph address,&lt;/span&gt;
    &lt;span class="c1"&gt;; so I don&amp;#39;t need it any more&lt;/span&gt;
    &lt;span class="ow"&gt;ld&lt;/span&gt; &lt;span class="nb"&gt;a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;de&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="ow"&gt;inc&lt;/span&gt; &lt;span class="nb"&gt;de&lt;/span&gt;

    &lt;span class="c1"&gt;; Copy into current chars&lt;/span&gt;
    &lt;span class="c1"&gt;; Part 1: Copy the left part into the current chars&lt;/span&gt;
    &lt;span class="ow"&gt;push&lt;/span&gt; &lt;span class="nb"&gt;af&lt;/span&gt;                     &lt;span class="c1"&gt;; stash width&lt;/span&gt;
    &lt;span class="c1"&gt;; A glyph is two chars or 32 bytes, so row_copy 32 times&lt;/span&gt;
    &lt;span class="ow"&gt;ld&lt;/span&gt; &lt;span class="nb"&gt;c&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;32&lt;/span&gt;
    &lt;span class="c1"&gt;; b is the next x position we&amp;#39;re free to write to.&lt;/span&gt;
    &lt;span class="c1"&gt;; Incrementing it here makes the inner loop simpler, since&lt;/span&gt;
    &lt;span class="c1"&gt;; it can&amp;#39;t be zero.  But it also means two jumps per loop,&lt;/span&gt;
    &lt;span class="c1"&gt;; so, ultimately this was a pretty silly idea.&lt;/span&gt;
    &lt;span class="ow"&gt;inc&lt;/span&gt; &lt;span class="nb"&gt;b&lt;/span&gt;
&lt;span class="nf"&gt;.row_copy:&lt;/span&gt;
    &lt;span class="ow"&gt;ld&lt;/span&gt; &lt;span class="nb"&gt;a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;de&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;                  &lt;span class="c1"&gt;; read next row of character&lt;/span&gt;

    &lt;span class="c1"&gt;; Shift right by b places with an inner loop&lt;/span&gt;
    &lt;span class="ow"&gt;push&lt;/span&gt; &lt;span class="nb"&gt;bc&lt;/span&gt;                     &lt;span class="c1"&gt;; preserve b while shifting&lt;/span&gt;
    &lt;span class="ow"&gt;dec&lt;/span&gt; &lt;span class="nb"&gt;b&lt;/span&gt;
&lt;span class="nf"&gt;.shift:&lt;/span&gt;                         &lt;span class="c1"&gt;; shift right by b bits&lt;/span&gt;
    &lt;span class="ow"&gt;jr&lt;/span&gt; &lt;span class="nv"&gt;z&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="err"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;done_shift&lt;/span&gt;
    &lt;span class="ow"&gt;srl&lt;/span&gt; &lt;span class="nb"&gt;a&lt;/span&gt;
    &lt;span class="ow"&gt;dec&lt;/span&gt; &lt;span class="nb"&gt;b&lt;/span&gt;
    &lt;span class="ow"&gt;jr&lt;/span&gt; &lt;span class="err"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;shift&lt;/span&gt;
&lt;span class="nf"&gt;.done_shift:&lt;/span&gt;
    &lt;span class="ow"&gt;pop&lt;/span&gt; &lt;span class="nb"&gt;bc&lt;/span&gt;

    &lt;span class="c1"&gt;; Write the updated byte to VRAM&lt;/span&gt;
    &lt;span class="ow"&gt;or&lt;/span&gt; &lt;span class="nb"&gt;a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;hl&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;                  &lt;span class="c1"&gt;; OR with current tile&lt;/span&gt;
    &lt;span class="ow"&gt;ld&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;hl&lt;/span&gt;&lt;span class="p"&gt;+],&lt;/span&gt; &lt;span class="nb"&gt;a&lt;/span&gt;
    &lt;span class="ow"&gt;inc&lt;/span&gt; &lt;span class="nb"&gt;de&lt;/span&gt;
    &lt;span class="ow"&gt;dec&lt;/span&gt; &lt;span class="nb"&gt;c&lt;/span&gt;
    &lt;span class="ow"&gt;jr&lt;/span&gt; &lt;span class="nv"&gt;nz&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="err"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;row_copy&lt;/span&gt;
    &lt;span class="ow"&gt;pop&lt;/span&gt; &lt;span class="nb"&gt;af&lt;/span&gt;                      &lt;span class="c1"&gt;; restore width&lt;/span&gt;

    &lt;span class="c1"&gt;; Part 2: Copy whatever&amp;#39;s left into the next char&lt;/span&gt;
    &lt;span class="c1"&gt;; TODO  :)&lt;/span&gt;

    &lt;span class="c1"&gt;; Cleanup for next iteration&lt;/span&gt;
    &lt;span class="c1"&gt;; Undo the b increment from way above&lt;/span&gt;
    &lt;span class="ow"&gt;dec&lt;/span&gt; &lt;span class="nb"&gt;b&lt;/span&gt;
    &lt;span class="c1"&gt;; It&amp;#39;s possible I overflowed into the next column, in which&lt;/span&gt;
    &lt;span class="c1"&gt;; case I want to leave hl where it is: pointing at the next&lt;/span&gt;
    &lt;span class="c1"&gt;; column.  Otherwise, I need to back it up to where it was.&lt;/span&gt;
    &lt;span class="c1"&gt;; Of course, I also need to update b, the x offset.&lt;/span&gt;
    &lt;span class="ow"&gt;add&lt;/span&gt; &lt;span class="nb"&gt;a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;b&lt;/span&gt;                    &lt;span class="c1"&gt;; a &amp;lt;- new x offset&lt;/span&gt;
    &lt;span class="c1"&gt;; If the new x offset is 8 or more, that&amp;#39;s actually the next&lt;/span&gt;
    &lt;span class="c1"&gt;; column&lt;/span&gt;
    &lt;span class="ow"&gt;cp&lt;/span&gt; &lt;span class="nb"&gt;a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;8&lt;/span&gt;
    &lt;span class="ow"&gt;jr&lt;/span&gt; &lt;span class="nv"&gt;nc&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="err"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;wrap_to_next_tile&lt;/span&gt;
    &lt;span class="ow"&gt;ld&lt;/span&gt; &lt;span class="nb"&gt;bc&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;-&lt;/span&gt;&lt;span class="nv"&gt;32&lt;/span&gt;                  &lt;span class="c1"&gt;; a &amp;lt; 8: back hl up&lt;/span&gt;
    &lt;span class="ow"&gt;add&lt;/span&gt; &lt;span class="nb"&gt;hl&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;bc&lt;/span&gt;
    &lt;span class="ow"&gt;jr&lt;/span&gt; &lt;span class="err"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;done_wrap&lt;/span&gt;
&lt;span class="nf"&gt;.wrap_to_next_tile:&lt;/span&gt;
    &lt;span class="ow"&gt;sub&lt;/span&gt; &lt;span class="nb"&gt;a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;8&lt;/span&gt;                    &lt;span class="c1"&gt;; a &amp;gt;= 8: subtract tile width&lt;/span&gt;
    &lt;span class="ow"&gt;ld&lt;/span&gt; &lt;span class="nb"&gt;b&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;a&lt;/span&gt;
&lt;span class="nf"&gt;.done_wrap:&lt;/span&gt;
    &lt;span class="c1"&gt;; Either way, store the new x offset into b&lt;/span&gt;
    &lt;span class="ow"&gt;ld&lt;/span&gt; &lt;span class="nb"&gt;b&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;a&lt;/span&gt;

    &lt;span class="c1"&gt;; And loop!&lt;/span&gt;
    &lt;span class="ow"&gt;pop&lt;/span&gt; &lt;span class="nb"&gt;de&lt;/span&gt;                      &lt;span class="c1"&gt;; pop text pointer&lt;/span&gt;
    &lt;span class="ow"&gt;jr&lt;/span&gt; &lt;span class="err"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;next_letter&lt;/span&gt;

&lt;span class="nf"&gt;.done:&lt;/span&gt;
    &lt;span class="c1"&gt;; Undo any goofy stuff I did, and get outta here&lt;/span&gt;
    &lt;span class="err"&gt;EnableLCD&lt;/span&gt;
    &lt;span class="c1"&gt;; Remember to reset bank to 0!&lt;/span&gt;
    &lt;span class="ow"&gt;xor&lt;/span&gt; &lt;span class="nb"&gt;a&lt;/span&gt;
    &lt;span class="ow"&gt;ldh&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;rVBK&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="nb"&gt;a&lt;/span&gt;
    &lt;span class="ow"&gt;ret&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;p&gt;Phew!  That was a lot, but hopefully it wasn&amp;#8217;t too bad.  I hit a few minor stumbling blocks, but as I recall, most of them were of the &amp;#8220;I get the conditions backwards every single time I use &lt;code&gt;cp&lt;/code&gt; augh&amp;#8221; flavor.  (In fact, if you look at the &lt;a href="https://github.com/eevee/anise-cheezball-rising/commit/3a8252f628e8c27c25ea28e2a1769f3429abe5f5"&gt;actual commit&lt;/a&gt; the above is based on, you may notice that I had the condition at the very end mixed up!  It&amp;#8217;s a miracle it managed to print part of the second letter at&amp;nbsp;all.)&lt;/p&gt;
&lt;p&gt;There are a &lt;em&gt;lot&lt;/em&gt; of caveats in this first pass, including that there&amp;#8217;s nothing to &lt;em&gt;erase&lt;/em&gt; the dialogue box and reshow the map underneath it.  (But I might end up using the window for this anyway, so there&amp;#8217;s no need for&amp;nbsp;that.)&lt;/p&gt;
&lt;p&gt;As a proof of concept, though, it&amp;#8217;s a great&amp;nbsp;start!&lt;/p&gt;
&lt;div class="prose-full-illustration"&gt;
&lt;img src="https://eev.ee/media/cheezball/06g-text-attempt-1.png" alt="Screenshot of Anise, with a black dialogue box that says: A|"&gt;
&lt;/div&gt;

&lt;p&gt;That&amp;#8217;s the letter &lt;code&gt;A&lt;/code&gt;, followed by the first two pixels of the letter &lt;code&gt;B&lt;/code&gt;.  I didn&amp;#8217;t implement the part where letters spill into the next column,&amp;nbsp;yet.&lt;/p&gt;
&lt;p&gt;Guess I&amp;#8217;d better do&amp;nbsp;that!&lt;/p&gt;
&lt;h2 id="second-pass"&gt;&lt;a class="toclink" href="#second-pass"&gt;Second&amp;nbsp;pass&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;One of the big problems with the first pass was that I had to turn the screen off to do the actual work safely.  Shifting a bunch of bytes by some amount is a &lt;em&gt;little&lt;/em&gt; slow, since I can only shift one bit at a time and have to do it within a loop, and vblank only lasts for about 6.5% of the entire duration of the frame.  If I continued like this, the screen would constantly flicker on and off every time I drew a new letter.&amp;nbsp;Yikes.&lt;/p&gt;
&lt;p&gt;I&amp;#8217;ll solve this the same way I solve pretty much any other vblank problem: do the actual work into a buffer, then just copy that buffer during vblank.  Since I intend to draw no more than one character per frame, and each character glyph is no wider than a single char column, I only need a buffer big enough to span two columns.  Text covers two rows, also, so that&amp;#8217;s four tiles&amp;nbsp;total.&lt;/p&gt;
&lt;p&gt;I also need to zero out the tile buffer when I first start drawing text — otherwise it may still have garbage left over from the last time text was displayed! — and this seems like a great opportunity to introduce a little &lt;code&gt;fill&lt;/code&gt; function.  Maybe then I&amp;#8217;ll do the right damn thing and clear out other stuff on&amp;nbsp;startup.&lt;/p&gt;
&lt;table class="highlighttable"&gt;&lt;tr&gt;&lt;td class="linenos"&gt;&lt;div class="linenodiv"&gt;&lt;pre&gt;&lt;span class="normal"&gt; 1&lt;/span&gt;
&lt;span class="normal"&gt; 2&lt;/span&gt;
&lt;span class="normal"&gt; 3&lt;/span&gt;
&lt;span class="normal"&gt; 4&lt;/span&gt;
&lt;span class="normal"&gt; 5&lt;/span&gt;
&lt;span class="normal"&gt; 6&lt;/span&gt;
&lt;span class="normal"&gt; 7&lt;/span&gt;
&lt;span class="normal"&gt; 8&lt;/span&gt;
&lt;span class="normal"&gt; 9&lt;/span&gt;
&lt;span class="normal"&gt;10&lt;/span&gt;
&lt;span class="normal"&gt;11&lt;/span&gt;
&lt;span class="normal"&gt;12&lt;/span&gt;
&lt;span class="normal"&gt;13&lt;/span&gt;
&lt;span class="normal"&gt;14&lt;/span&gt;
&lt;span class="normal"&gt;15&lt;/span&gt;
&lt;span class="normal"&gt;16&lt;/span&gt;
&lt;span class="normal"&gt;17&lt;/span&gt;
&lt;span class="normal"&gt;18&lt;/span&gt;
&lt;span class="normal"&gt;19&lt;/span&gt;
&lt;span class="normal"&gt;20&lt;/span&gt;
&lt;span class="normal"&gt;21&lt;/span&gt;
&lt;span class="normal"&gt;22&lt;/span&gt;
&lt;span class="normal"&gt;23&lt;/span&gt;
&lt;span class="normal"&gt;24&lt;/span&gt;
&lt;span class="normal"&gt;25&lt;/span&gt;
&lt;span class="normal"&gt;26&lt;/span&gt;
&lt;span class="normal"&gt;27&lt;/span&gt;
&lt;span class="normal"&gt;28&lt;/span&gt;
&lt;span class="normal"&gt;29&lt;/span&gt;
&lt;span class="normal"&gt;30&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/td&gt;&lt;td class="code"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="c1"&gt;; Utility code section&lt;/span&gt;

&lt;span class="c1"&gt;; fill c bytes starting at hl with a&lt;/span&gt;
&lt;span class="c1"&gt;; NOTE: c must not be zero&lt;/span&gt;
&lt;span class="nf"&gt;fill:&lt;/span&gt;
    &lt;span class="ow"&gt;ld&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;hl&lt;/span&gt;&lt;span class="p"&gt;+],&lt;/span&gt; &lt;span class="nb"&gt;a&lt;/span&gt;
    &lt;span class="ow"&gt;dec&lt;/span&gt; &lt;span class="nb"&gt;c&lt;/span&gt;
    &lt;span class="ow"&gt;jr&lt;/span&gt; &lt;span class="nv"&gt;nz&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;fill&lt;/span&gt;
    &lt;span class="ow"&gt;ret&lt;/span&gt;

&lt;span class="c1"&gt;; ...&lt;/span&gt;

&lt;span class="c1"&gt;; Stick this at a fixed nice address for now, just so it&amp;#39;s easy&lt;/span&gt;
&lt;span class="c1"&gt;; for me to look at and debug&lt;/span&gt;
&lt;span class="k"&gt;SECTION &amp;quot;Text buffer&amp;quot;, WRAM0[$C200]&lt;/span&gt;
&lt;span class="nf"&gt;text_buffer:&lt;/span&gt;
    &lt;span class="c1"&gt;; Text is up to 8x16 but may span two columns, so carve out&lt;/span&gt;
    &lt;span class="c1"&gt;; enough space for four tiles&lt;/span&gt;
    &lt;span class="k"&gt;ds $40&lt;/span&gt;

&lt;span class="nf"&gt;show_dialogue:&lt;/span&gt;
    &lt;span class="err"&gt;DisableLCD&lt;/span&gt;
    &lt;span class="c1"&gt;; ... setup stuff ...&lt;/span&gt;
    &lt;span class="err"&gt;EnableLCD&lt;/span&gt;

    &lt;span class="c1"&gt;; Zero out the tile buffer&lt;/span&gt;
    &lt;span class="ow"&gt;xor&lt;/span&gt; &lt;span class="nb"&gt;a&lt;/span&gt;
    &lt;span class="ow"&gt;ld&lt;/span&gt; &lt;span class="nb"&gt;hl&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;text_buffer&lt;/span&gt;
    &lt;span class="ow"&gt;ld&lt;/span&gt; &lt;span class="nb"&gt;c&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mh"&gt;$40&lt;/span&gt;
    &lt;span class="ow"&gt;call&lt;/span&gt; &lt;span class="nv"&gt;fill&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;p&gt;That first round of disabling and enabling the &lt;span class="caps"&gt;LCD&lt;/span&gt; is still necessary, because the setup work takes a little time, but I can get rid of that later too.  For now, the priority is fixing the text scroll (and supporting text that spans more than one&amp;nbsp;tile).&lt;/p&gt;
&lt;p&gt;The code is the same up until I start copying the glyph into the tiles.  Now it doesn&amp;#8217;t go to &lt;span class="caps"&gt;VRAM&lt;/span&gt;, but into the&amp;nbsp;buffer.&lt;/p&gt;
&lt;p&gt;There&amp;#8217;s another change here, too.  Previously, I shifted the glyph right, letting bits fall off the right end and disappear.  But the bits that drop off the end are exactly the bits that I need to draw to the &lt;em&gt;next&lt;/em&gt; char.  I could do a &lt;em&gt;left&lt;/em&gt; shift to retrieve them, but I had a different idea: &lt;em&gt;rotate&lt;/em&gt; the glyph&amp;nbsp;instead.&lt;/p&gt;
&lt;p&gt;Say I want to draw a glyph offset by 3 pixels.  Then I want to do&amp;nbsp;this:&lt;/p&gt;
&lt;table class="highlighttable"&gt;&lt;tr&gt;&lt;td class="linenos"&gt;&lt;div class="linenodiv"&gt;&lt;pre&gt;&lt;span class="normal"&gt;1&lt;/span&gt;
&lt;span class="normal"&gt;2&lt;/span&gt;
&lt;span class="normal"&gt;3&lt;/span&gt;
&lt;span class="normal"&gt;4&lt;/span&gt;
&lt;span class="normal"&gt;5&lt;/span&gt;
&lt;span class="normal"&gt;6&lt;/span&gt;
&lt;span class="normal"&gt;7&lt;/span&gt;
&lt;span class="normal"&gt;8&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/td&gt;&lt;td class="code"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="n"&gt;abcdefgh&lt;/span&gt;  &lt;span class="o"&gt;&amp;lt;-&lt;/span&gt; &lt;span class="n"&gt;original&lt;/span&gt; &lt;span class="n"&gt;glyph&lt;/span&gt; &lt;span class="n"&gt;bits&lt;/span&gt;
&lt;span class="n"&gt;fghabcde&lt;/span&gt;  &lt;span class="o"&gt;&amp;lt;-&lt;/span&gt; &lt;span class="n"&gt;rotate&lt;/span&gt; &lt;span class="n"&gt;right&lt;/span&gt; &lt;span class="m"&gt;3&lt;/span&gt;
&lt;span class="m"&gt;00011111&lt;/span&gt;  &lt;span class="o"&gt;&amp;lt;-&lt;/span&gt; &lt;span class="n"&gt;mask&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;which&lt;/span&gt; &lt;span class="n"&gt;is&lt;/span&gt; &lt;span class="n"&gt;just&lt;/span&gt; &lt;span class="o"&gt;$&lt;/span&gt;&lt;span class="n"&gt;ff&lt;/span&gt; &lt;span class="n"&gt;shifted&lt;/span&gt; &lt;span class="n"&gt;right&lt;/span&gt; &lt;span class="m"&gt;3&lt;/span&gt;

&lt;span class="m"&gt;000&lt;/span&gt;&lt;span class="n"&gt;abcde&lt;/span&gt;  &lt;span class="o"&gt;&amp;lt;-&lt;/span&gt; &lt;span class="n"&gt;rotated&lt;/span&gt; &lt;span class="n"&gt;glyph&lt;/span&gt; &lt;span class="n"&gt;AND&lt;/span&gt; &lt;span class="n"&gt;mask&lt;/span&gt; &lt;span class="n"&gt;gives&lt;/span&gt; &lt;span class="n"&gt;the&lt;/span&gt; &lt;span class="n"&gt;left&lt;/span&gt; &lt;span class="n"&gt;part&lt;/span&gt;

&lt;span class="m"&gt;11100000&lt;/span&gt;  &lt;span class="o"&gt;&amp;lt;-&lt;/span&gt; &lt;span class="n"&gt;mask&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;inverted&lt;/span&gt;
&lt;span class="n"&gt;fgh00000&lt;/span&gt;  &lt;span class="o"&gt;&amp;lt;-&lt;/span&gt; &lt;span class="n"&gt;rotated&lt;/span&gt; &lt;span class="n"&gt;glyph&lt;/span&gt; &lt;span class="n"&gt;AND&lt;/span&gt; &lt;span class="n"&gt;inverted&lt;/span&gt; &lt;span class="n"&gt;mask&lt;/span&gt; &lt;span class="n"&gt;gives&lt;/span&gt; &lt;span class="n"&gt;the&lt;/span&gt; &lt;span class="n"&gt;right&lt;/span&gt; &lt;span class="n"&gt;part&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;p&gt;The time and code savings aren&amp;#8217;t huge, exactly, and nothing else is going on while text is rendering so it&amp;#8217;s not like time is at a premium here.  But hey this feels clever so let&amp;#8217;s do&amp;nbsp;it.&lt;/p&gt;
&lt;table class="highlighttable"&gt;&lt;tr&gt;&lt;td class="linenos"&gt;&lt;div class="linenodiv"&gt;&lt;pre&gt;&lt;span class="normal"&gt; 1&lt;/span&gt;
&lt;span class="normal"&gt; 2&lt;/span&gt;
&lt;span class="normal"&gt; 3&lt;/span&gt;
&lt;span class="normal"&gt; 4&lt;/span&gt;
&lt;span class="normal"&gt; 5&lt;/span&gt;
&lt;span class="normal"&gt; 6&lt;/span&gt;
&lt;span class="normal"&gt; 7&lt;/span&gt;
&lt;span class="normal"&gt; 8&lt;/span&gt;
&lt;span class="normal"&gt; 9&lt;/span&gt;
&lt;span class="normal"&gt;10&lt;/span&gt;
&lt;span class="normal"&gt;11&lt;/span&gt;
&lt;span class="normal"&gt;12&lt;/span&gt;
&lt;span class="normal"&gt;13&lt;/span&gt;
&lt;span class="normal"&gt;14&lt;/span&gt;
&lt;span class="normal"&gt;15&lt;/span&gt;
&lt;span class="normal"&gt;16&lt;/span&gt;
&lt;span class="normal"&gt;17&lt;/span&gt;
&lt;span class="normal"&gt;18&lt;/span&gt;
&lt;span class="normal"&gt;19&lt;/span&gt;
&lt;span class="normal"&gt;20&lt;/span&gt;
&lt;span class="normal"&gt;21&lt;/span&gt;
&lt;span class="normal"&gt;22&lt;/span&gt;
&lt;span class="normal"&gt;23&lt;/span&gt;
&lt;span class="normal"&gt;24&lt;/span&gt;
&lt;span class="normal"&gt;25&lt;/span&gt;
&lt;span class="normal"&gt;26&lt;/span&gt;
&lt;span class="normal"&gt;27&lt;/span&gt;
&lt;span class="normal"&gt;28&lt;/span&gt;
&lt;span class="normal"&gt;29&lt;/span&gt;
&lt;span class="normal"&gt;30&lt;/span&gt;
&lt;span class="normal"&gt;31&lt;/span&gt;
&lt;span class="normal"&gt;32&lt;/span&gt;
&lt;span class="normal"&gt;33&lt;/span&gt;
&lt;span class="normal"&gt;34&lt;/span&gt;
&lt;span class="normal"&gt;35&lt;/span&gt;
&lt;span class="normal"&gt;36&lt;/span&gt;
&lt;span class="normal"&gt;37&lt;/span&gt;
&lt;span class="normal"&gt;38&lt;/span&gt;
&lt;span class="normal"&gt;39&lt;/span&gt;
&lt;span class="normal"&gt;40&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/td&gt;&lt;td class="code"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;    &lt;span class="c1"&gt;; Copy into current chars&lt;/span&gt;
    &lt;span class="ow"&gt;push&lt;/span&gt; &lt;span class="nb"&gt;af&lt;/span&gt;                     &lt;span class="c1"&gt;; stash width&lt;/span&gt;
    &lt;span class="ow"&gt;ld&lt;/span&gt; &lt;span class="nb"&gt;c&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;32&lt;/span&gt;                    &lt;span class="c1"&gt;; 32 bytes per row&lt;/span&gt;
    &lt;span class="ow"&gt;ld&lt;/span&gt; &lt;span class="nb"&gt;hl&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;text_buffer&lt;/span&gt;          &lt;span class="c1"&gt;; new!&lt;/span&gt;
    &lt;span class="c1"&gt;; This is still silly.&lt;/span&gt;
    &lt;span class="ow"&gt;inc&lt;/span&gt; &lt;span class="nb"&gt;b&lt;/span&gt;
&lt;span class="nf"&gt;.row_copy:&lt;/span&gt;
    &lt;span class="ow"&gt;ld&lt;/span&gt; &lt;span class="nb"&gt;a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;de&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;                  &lt;span class="c1"&gt;; read next row of character&lt;/span&gt;
    &lt;span class="c1"&gt;; Rotate right by b - 1 pixels -- remember, b contains the&lt;/span&gt;
    &lt;span class="c1"&gt;; x-offset within the current tile where to start drawing&lt;/span&gt;
    &lt;span class="ow"&gt;push&lt;/span&gt; &lt;span class="nb"&gt;bc&lt;/span&gt;                     &lt;span class="c1"&gt;; preserve b while shifting&lt;/span&gt;
    &lt;span class="ow"&gt;ld&lt;/span&gt; &lt;span class="nb"&gt;c&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mh"&gt;$ff&lt;/span&gt;                   &lt;span class="c1"&gt;; initialize the mask&lt;/span&gt;
    &lt;span class="ow"&gt;dec&lt;/span&gt; &lt;span class="nb"&gt;b&lt;/span&gt;
    &lt;span class="ow"&gt;jr&lt;/span&gt; &lt;span class="nv"&gt;z&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="err"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;skip_rotate&lt;/span&gt;
&lt;span class="nf"&gt;.rotate:&lt;/span&gt;
    &lt;span class="c1"&gt;; Rotate the glyph (a), but shift the mask (c), so that the&lt;/span&gt;
    &lt;span class="c1"&gt;; left end of the mask fills up with zeroes&lt;/span&gt;
    &lt;span class="ow"&gt;rrca&lt;/span&gt;
    &lt;span class="ow"&gt;srl&lt;/span&gt; &lt;span class="nb"&gt;c&lt;/span&gt;
    &lt;span class="ow"&gt;dec&lt;/span&gt; &lt;span class="nb"&gt;b&lt;/span&gt;
    &lt;span class="ow"&gt;jr&lt;/span&gt; &lt;span class="nv"&gt;nz&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="err"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;rotate&lt;/span&gt;
&lt;span class="nf"&gt;.skip_rotate:&lt;/span&gt;
    &lt;span class="ow"&gt;push&lt;/span&gt; &lt;span class="nb"&gt;af&lt;/span&gt;                     &lt;span class="c1"&gt;; preserve glyph&lt;/span&gt;
    &lt;span class="ow"&gt;and&lt;/span&gt; &lt;span class="nb"&gt;a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;c&lt;/span&gt;                    &lt;span class="c1"&gt;; mask right pixels&lt;/span&gt;
    &lt;span class="c1"&gt;; Draw to left half of text buffer&lt;/span&gt;
    &lt;span class="ow"&gt;or&lt;/span&gt; &lt;span class="nb"&gt;a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;hl&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;                  &lt;span class="c1"&gt;; OR with current tile&lt;/span&gt;
    &lt;span class="ow"&gt;ld&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;hl&lt;/span&gt;&lt;span class="p"&gt;+],&lt;/span&gt; &lt;span class="nb"&gt;a&lt;/span&gt;
    &lt;span class="c1"&gt;; Write the remaining bits to right half&lt;/span&gt;
    &lt;span class="ow"&gt;ld&lt;/span&gt; &lt;span class="nb"&gt;a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;c&lt;/span&gt;                     &lt;span class="c1"&gt;; put mask in a...&lt;/span&gt;
    &lt;span class="ow"&gt;cpl&lt;/span&gt;                         &lt;span class="c1"&gt;; ...to invert it&lt;/span&gt;
    &lt;span class="ow"&gt;ld&lt;/span&gt; &lt;span class="nb"&gt;c&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;a&lt;/span&gt;                     &lt;span class="c1"&gt;; then put it back&lt;/span&gt;
    &lt;span class="ow"&gt;pop&lt;/span&gt; &lt;span class="nb"&gt;af&lt;/span&gt;                      &lt;span class="c1"&gt;; restore unmasked glyph&lt;/span&gt;
    &lt;span class="ow"&gt;and&lt;/span&gt; &lt;span class="nb"&gt;a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;c&lt;/span&gt;                    &lt;span class="c1"&gt;; mask left pixels&lt;/span&gt;
    &lt;span class="ow"&gt;ld&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;hl&lt;/span&gt;&lt;span class="p"&gt;+],&lt;/span&gt; &lt;span class="nb"&gt;a&lt;/span&gt;                 &lt;span class="c1"&gt;; and store them!&lt;/span&gt;
    &lt;span class="c1"&gt;; Clean up after myself, and loop to the next row&lt;/span&gt;
    &lt;span class="ow"&gt;inc&lt;/span&gt; &lt;span class="nb"&gt;de&lt;/span&gt;                      &lt;span class="c1"&gt;; next row of glyph&lt;/span&gt;
    &lt;span class="ow"&gt;pop&lt;/span&gt; &lt;span class="nb"&gt;bc&lt;/span&gt;                      &lt;span class="c1"&gt;; restore counter!&lt;/span&gt;
    &lt;span class="ow"&gt;dec&lt;/span&gt; &lt;span class="nb"&gt;c&lt;/span&gt;
    &lt;span class="ow"&gt;jr&lt;/span&gt; &lt;span class="nv"&gt;nz&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="err"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;row_copy&lt;/span&gt;
    &lt;span class="ow"&gt;pop&lt;/span&gt; &lt;span class="nb"&gt;af&lt;/span&gt;                      &lt;span class="c1"&gt;; restore width&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;p&gt;The use of the stack is a &lt;em&gt;little&lt;/em&gt; confusing (and don&amp;#8217;t worry, it only gets worse in later posts).  Note for example that &lt;code&gt;c&lt;/code&gt; is used as the loop counter, but since I don&amp;#8217;t actually need its value &lt;em&gt;within&lt;/em&gt; the body of the loop, I can &lt;code&gt;push&lt;/code&gt; it right at the beginning and use &lt;code&gt;c&lt;/code&gt; to hold the mask, then &lt;code&gt;pop&lt;/code&gt; the loop counter back into place at the&amp;nbsp;end.&lt;/p&gt;
&lt;p&gt;(&lt;strong&gt;&lt;span class="caps"&gt;UPDATE&lt;/span&gt;&lt;/strong&gt;: A reader points out that I don&amp;#8217;t really need the mask at all.  The &lt;code&gt;rrca&lt;/code&gt; instruction puts the lost bit in the carry flag, so I can instead follow it with &lt;code&gt;rr c&lt;/code&gt;, which puts the carry flag into bit 7 of &lt;code&gt;c&lt;/code&gt;.  Then I&amp;#8217;ll end up with the right bits in &lt;code&gt;c&lt;/code&gt;, no masking required.  If I also used &lt;code&gt;rra&lt;/code&gt; instead of &lt;code&gt;rrca&lt;/code&gt;, then &lt;code&gt;a&lt;/code&gt; would end up with just the left bits, and nothing needs masking at&amp;nbsp;all!)&lt;/p&gt;
&lt;p&gt;(Also, it occurs to me that I could avoid the loop entirely with a Duff&amp;#8217;s device&amp;#8230;  but that might be a little over the&amp;nbsp;top.)&lt;/p&gt;
&lt;p&gt;This is where I first started to feel register pressure, especially when addresses eat up &lt;em&gt;two&lt;/em&gt; of them.  My options are pretty limited: I can store stuff on the stack, or store stuff in &lt;span class="caps"&gt;RAM&lt;/span&gt;.  The stack is arguably harder to follow (and easier to fuck up, which I&amp;#8217;ve done several times), but either way there&amp;#8217;s the register&amp;nbsp;ambiguity.&lt;/p&gt;
&lt;p&gt;Which is shorter/faster?&amp;nbsp;Well:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;A &lt;code&gt;push&lt;/code&gt;/&lt;code&gt;pop&lt;/code&gt; pair takes 2 bytes and 7&amp;nbsp;cycles.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Immediate writing to &lt;span class="caps"&gt;RAM&lt;/span&gt; and immediate reading back from it takes 6 bytes and 8 cycles, &lt;em&gt;and&lt;/em&gt; can only be done with &lt;code&gt;a&lt;/code&gt;, so I&amp;#8217;d probably have to copy into and out of some other register&amp;nbsp;too.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Putting an address in &lt;code&gt;hl&lt;/code&gt;, writing to it, then reading from it takes 5 bytes and 7 cycles, &lt;em&gt;but&lt;/em&gt; requires that I can preserve &lt;code&gt;hl&lt;/code&gt;.  (On the other hand, if I can preserve the value of &lt;code&gt;hl&lt;/code&gt; across a loop or something, then it&amp;#8217;s amortized away and the read/write is only 2 bytes and 3 cycles.  But if that&amp;#8217;s the case, chances are that I&amp;#8217;m not under enough register pressure to need using &lt;span class="caps"&gt;RAM&lt;/span&gt; in the first&amp;nbsp;place.)&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Parts of high &lt;span class="caps"&gt;RAM&lt;/span&gt; ($ff80 and up) are available for program use, and they can be read or written with the same instructions that operate on the control knobs starting at $ff00.  A high &lt;span class="caps"&gt;RAM&lt;/span&gt; read and write takes 4 bytes and 6 cycles, which isn&amp;#8217;t too bad, but once again I have to go through the &lt;code&gt;a&lt;/code&gt; register so I&amp;#8217;ll probably need some other&amp;nbsp;copies.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Stack it is,&amp;nbsp;then.&lt;/p&gt;
&lt;p&gt;Anyway!  Where were we.  I need to now copy the buffer into &lt;span class="caps"&gt;VRAM&lt;/span&gt;.&lt;/p&gt;
&lt;p&gt;You may have noticed that the buffer isn&amp;#8217;t quite populated in char format.  Instead, it&amp;#8217;s populated like one big 16-pixel char, with the first 16 bits corresponding to the 16 pixels spanning &lt;em&gt;both&lt;/em&gt; columns.  &lt;span class="caps"&gt;VRAM&lt;/span&gt;, of course, expects to get all the pixels from the first column, then all the pixels from the second column.  If that&amp;#8217;s not clear, here&amp;#8217;s what I have (where the bits are in order from left to right, top to&amp;nbsp;bottom):&lt;/p&gt;
&lt;table class="highlighttable"&gt;&lt;tr&gt;&lt;td class="linenos"&gt;&lt;div class="linenodiv"&gt;&lt;pre&gt;&lt;span class="normal"&gt;1&lt;/span&gt;
&lt;span class="normal"&gt;2&lt;/span&gt;
&lt;span class="normal"&gt;3&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/td&gt;&lt;td class="code"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="n"&gt;AAAAAAAA&lt;/span&gt; &lt;span class="n"&gt;BBBBBBBB&lt;/span&gt;  &lt;span class="o"&gt;&amp;lt;-&lt;/span&gt; &lt;span class="n"&gt;high&lt;/span&gt; &lt;span class="n"&gt;bits&lt;/span&gt; &lt;span class="n"&gt;for&lt;/span&gt; &lt;span class="n"&gt;first&lt;/span&gt; &lt;span class="n"&gt;row&lt;/span&gt; &lt;span class="n"&gt;of&lt;/span&gt; &lt;span class="n"&gt;pixels&lt;/span&gt;
&lt;span class="n"&gt;aaaaaaaa&lt;/span&gt; &lt;span class="n"&gt;bbbbbbbb&lt;/span&gt;  &lt;span class="o"&gt;&amp;lt;-&lt;/span&gt; &lt;span class="n"&gt;low&lt;/span&gt; &lt;span class="n"&gt;bits&lt;/span&gt; &lt;span class="n"&gt;for&lt;/span&gt; &lt;span class="n"&gt;first&lt;/span&gt; &lt;span class="n"&gt;row&lt;/span&gt; &lt;span class="n"&gt;of&lt;/span&gt; &lt;span class="n"&gt;pixels&lt;/span&gt;
&lt;span class="kc"&gt;...&lt;/span&gt; &lt;span class="n"&gt;other&lt;/span&gt; &lt;span class="n"&gt;rows&lt;/span&gt; &lt;span class="kc"&gt;...&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;p&gt;And here&amp;#8217;s what I need to put in &lt;span class="caps"&gt;VRAM&lt;/span&gt;:&lt;/p&gt;
&lt;table class="highlighttable"&gt;&lt;tr&gt;&lt;td class="linenos"&gt;&lt;div class="linenodiv"&gt;&lt;pre&gt;&lt;span class="normal"&gt;1&lt;/span&gt;
&lt;span class="normal"&gt;2&lt;/span&gt;
&lt;span class="normal"&gt;3&lt;/span&gt;
&lt;span class="normal"&gt;4&lt;/span&gt;
&lt;span class="normal"&gt;5&lt;/span&gt;
&lt;span class="normal"&gt;6&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/td&gt;&lt;td class="code"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="n"&gt;AAAAAAAA&lt;/span&gt;  &lt;span class="o"&gt;&amp;lt;-&lt;/span&gt; &lt;span class="n"&gt;high&lt;/span&gt; &lt;span class="n"&gt;bits&lt;/span&gt; &lt;span class="n"&gt;for&lt;/span&gt; &lt;span class="n"&gt;first&lt;/span&gt; &lt;span class="n"&gt;row&lt;/span&gt; &lt;span class="n"&gt;of&lt;/span&gt; &lt;span class="n"&gt;left&lt;/span&gt; &lt;span class="n"&gt;column&lt;/span&gt; &lt;span class="n"&gt;of&lt;/span&gt; &lt;span class="n"&gt;pixels&lt;/span&gt;
&lt;span class="n"&gt;aaaaaaaa&lt;/span&gt;  &lt;span class="o"&gt;&amp;lt;-&lt;/span&gt; &lt;span class="n"&gt;low&lt;/span&gt; &lt;span class="n"&gt;bits&lt;/span&gt; &lt;span class="n"&gt;for&lt;/span&gt; &lt;span class="n"&gt;first&lt;/span&gt; &lt;span class="n"&gt;row&lt;/span&gt; &lt;span class="n"&gt;of&lt;/span&gt; &lt;span class="n"&gt;left&lt;/span&gt; &lt;span class="n"&gt;column&lt;/span&gt; &lt;span class="n"&gt;of&lt;/span&gt; &lt;span class="n"&gt;pixels&lt;/span&gt;
&lt;span class="kc"&gt;...&lt;/span&gt; &lt;span class="n"&gt;other&lt;/span&gt; &lt;span class="n"&gt;rows&lt;/span&gt; &lt;span class="n"&gt;of&lt;/span&gt; &lt;span class="n"&gt;left&lt;/span&gt; &lt;span class="n"&gt;column&lt;/span&gt; &lt;span class="kc"&gt;...&lt;/span&gt;
&lt;span class="n"&gt;BBBBBBBB&lt;/span&gt;  &lt;span class="o"&gt;&amp;lt;-&lt;/span&gt; &lt;span class="n"&gt;high&lt;/span&gt; &lt;span class="n"&gt;bits&lt;/span&gt; &lt;span class="n"&gt;for&lt;/span&gt; &lt;span class="n"&gt;first&lt;/span&gt; &lt;span class="n"&gt;row&lt;/span&gt; &lt;span class="n"&gt;of&lt;/span&gt; &lt;span class="n"&gt;right&lt;/span&gt; &lt;span class="n"&gt;column&lt;/span&gt; &lt;span class="n"&gt;of&lt;/span&gt; &lt;span class="n"&gt;pixels&lt;/span&gt;
&lt;span class="n"&gt;bbbbbbbb&lt;/span&gt;  &lt;span class="o"&gt;&amp;lt;-&lt;/span&gt; &lt;span class="n"&gt;low&lt;/span&gt; &lt;span class="n"&gt;bits&lt;/span&gt; &lt;span class="n"&gt;for&lt;/span&gt; &lt;span class="n"&gt;first&lt;/span&gt; &lt;span class="n"&gt;row&lt;/span&gt; &lt;span class="n"&gt;of&lt;/span&gt; &lt;span class="n"&gt;right&lt;/span&gt; &lt;span class="n"&gt;column&lt;/span&gt; &lt;span class="n"&gt;of&lt;/span&gt; &lt;span class="n"&gt;pixels&lt;/span&gt;
&lt;span class="kc"&gt;...&lt;/span&gt; &lt;span class="n"&gt;other&lt;/span&gt; &lt;span class="n"&gt;rows&lt;/span&gt; &lt;span class="n"&gt;of&lt;/span&gt; &lt;span class="n"&gt;right&lt;/span&gt; &lt;span class="n"&gt;column&lt;/span&gt; &lt;span class="kc"&gt;...&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;p&gt;I hope that makes sense!  To fix this, I use two loops (one for each column), and in each loop I copy &lt;em&gt;every other&lt;/em&gt; byte into &lt;span class="caps"&gt;VRAM&lt;/span&gt;.  That deinterlaces the&amp;nbsp;buffer.&lt;/p&gt;
&lt;table class="highlighttable"&gt;&lt;tr&gt;&lt;td class="linenos"&gt;&lt;div class="linenodiv"&gt;&lt;pre&gt;&lt;span class="normal"&gt; 1&lt;/span&gt;
&lt;span class="normal"&gt; 2&lt;/span&gt;
&lt;span class="normal"&gt; 3&lt;/span&gt;
&lt;span class="normal"&gt; 4&lt;/span&gt;
&lt;span class="normal"&gt; 5&lt;/span&gt;
&lt;span class="normal"&gt; 6&lt;/span&gt;
&lt;span class="normal"&gt; 7&lt;/span&gt;
&lt;span class="normal"&gt; 8&lt;/span&gt;
&lt;span class="normal"&gt; 9&lt;/span&gt;
&lt;span class="normal"&gt;10&lt;/span&gt;
&lt;span class="normal"&gt;11&lt;/span&gt;
&lt;span class="normal"&gt;12&lt;/span&gt;
&lt;span class="normal"&gt;13&lt;/span&gt;
&lt;span class="normal"&gt;14&lt;/span&gt;
&lt;span class="normal"&gt;15&lt;/span&gt;
&lt;span class="normal"&gt;16&lt;/span&gt;
&lt;span class="normal"&gt;17&lt;/span&gt;
&lt;span class="normal"&gt;18&lt;/span&gt;
&lt;span class="normal"&gt;19&lt;/span&gt;
&lt;span class="normal"&gt;20&lt;/span&gt;
&lt;span class="normal"&gt;21&lt;/span&gt;
&lt;span class="normal"&gt;22&lt;/span&gt;
&lt;span class="normal"&gt;23&lt;/span&gt;
&lt;span class="normal"&gt;24&lt;/span&gt;
&lt;span class="normal"&gt;25&lt;/span&gt;
&lt;span class="normal"&gt;26&lt;/span&gt;
&lt;span class="normal"&gt;27&lt;/span&gt;
&lt;span class="normal"&gt;28&lt;/span&gt;
&lt;span class="normal"&gt;29&lt;/span&gt;
&lt;span class="normal"&gt;30&lt;/span&gt;
&lt;span class="normal"&gt;31&lt;/span&gt;
&lt;span class="normal"&gt;32&lt;/span&gt;
&lt;span class="normal"&gt;33&lt;/span&gt;
&lt;span class="normal"&gt;34&lt;/span&gt;
&lt;span class="normal"&gt;35&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/td&gt;&lt;td class="code"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;    &lt;span class="c1"&gt;; Draw the buffered tiles to vram&lt;/span&gt;
    &lt;span class="c1"&gt;; The text buffer is treated like it&amp;#39;s 16 pixels wide, but&lt;/span&gt;
    &lt;span class="c1"&gt;; VRAM is of course only 8 pixels wide, so we need to do&lt;/span&gt;
    &lt;span class="c1"&gt;; this in two iterations: the left two tiles, then the right&lt;/span&gt;
    &lt;span class="ow"&gt;pop&lt;/span&gt; &lt;span class="nb"&gt;hl&lt;/span&gt;                      &lt;span class="c1"&gt;; restore hl (VRAM)&lt;/span&gt;
    &lt;span class="ow"&gt;push&lt;/span&gt; &lt;span class="nb"&gt;af&lt;/span&gt;                     &lt;span class="c1"&gt;; stash width, again&lt;/span&gt;
    &lt;span class="ow"&gt;call&lt;/span&gt; &lt;span class="nv"&gt;wait_for_vblank&lt;/span&gt;        &lt;span class="c1"&gt;; always wait before drawing&lt;/span&gt;
    &lt;span class="ow"&gt;push&lt;/span&gt; &lt;span class="nb"&gt;bc&lt;/span&gt;
    &lt;span class="ow"&gt;push&lt;/span&gt; &lt;span class="nb"&gt;de&lt;/span&gt;
    &lt;span class="c1"&gt;; Draw the left two tiles&lt;/span&gt;
    &lt;span class="ow"&gt;ld&lt;/span&gt; &lt;span class="nb"&gt;c&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mh"&gt;$20&lt;/span&gt;
    &lt;span class="ow"&gt;ld&lt;/span&gt; &lt;span class="nb"&gt;de&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;text_buffer&lt;/span&gt;
&lt;span class="nf"&gt;.draw_left:&lt;/span&gt;
    &lt;span class="ow"&gt;ld&lt;/span&gt; &lt;span class="nb"&gt;a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;de&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="c1"&gt;; This double inc fixes the interlacing&lt;/span&gt;
    &lt;span class="ow"&gt;inc&lt;/span&gt; &lt;span class="nb"&gt;de&lt;/span&gt;
    &lt;span class="ow"&gt;inc&lt;/span&gt; &lt;span class="nb"&gt;de&lt;/span&gt;
    &lt;span class="ow"&gt;ld&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;hl&lt;/span&gt;&lt;span class="p"&gt;+],&lt;/span&gt; &lt;span class="nb"&gt;a&lt;/span&gt;
    &lt;span class="ow"&gt;dec&lt;/span&gt; &lt;span class="nb"&gt;c&lt;/span&gt;
    &lt;span class="ow"&gt;jr&lt;/span&gt; &lt;span class="nv"&gt;nz&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="err"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;draw_left&lt;/span&gt;
    &lt;span class="c1"&gt;; Draw the right two tiles&lt;/span&gt;
    &lt;span class="ow"&gt;ld&lt;/span&gt; &lt;span class="nb"&gt;c&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mh"&gt;$20&lt;/span&gt;
    &lt;span class="c1"&gt;; This time, start from the SECOND byte, which will grab&lt;/span&gt;
    &lt;span class="c1"&gt;; all the bytes skipped by the previous loop&lt;/span&gt;
    &lt;span class="ow"&gt;ld&lt;/span&gt; &lt;span class="nb"&gt;de&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;text_buffer&lt;/span&gt; &lt;span class="p"&gt;+&lt;/span&gt; &lt;span class="nv"&gt;1&lt;/span&gt;
&lt;span class="nf"&gt;.draw_right:&lt;/span&gt;
    &lt;span class="ow"&gt;ld&lt;/span&gt; &lt;span class="nb"&gt;a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;de&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="ow"&gt;inc&lt;/span&gt; &lt;span class="nb"&gt;de&lt;/span&gt;
    &lt;span class="ow"&gt;inc&lt;/span&gt; &lt;span class="nb"&gt;de&lt;/span&gt;
    &lt;span class="ow"&gt;ld&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;hl&lt;/span&gt;&lt;span class="p"&gt;+],&lt;/span&gt; &lt;span class="nb"&gt;a&lt;/span&gt;
    &lt;span class="ow"&gt;dec&lt;/span&gt; &lt;span class="nb"&gt;c&lt;/span&gt;
    &lt;span class="ow"&gt;jr&lt;/span&gt; &lt;span class="nv"&gt;nz&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="err"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;draw_right&lt;/span&gt;
    &lt;span class="ow"&gt;pop&lt;/span&gt; &lt;span class="nb"&gt;de&lt;/span&gt;
    &lt;span class="ow"&gt;pop&lt;/span&gt; &lt;span class="nb"&gt;bc&lt;/span&gt;
    &lt;span class="ow"&gt;pop&lt;/span&gt; &lt;span class="nb"&gt;af&lt;/span&gt;                      &lt;span class="c1"&gt;; restore width, again&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;p&gt;Just about done!  There&amp;#8217;s one last thing to do before looping to the next character.  If this character did in fact span both columns, then the buffer needs to be moved to the left by one column.  Here&amp;#8217;s a simplified diagram, pretending chars are 5×5 and I just drew a&amp;nbsp;B:&lt;/p&gt;
&lt;table class="highlighttable"&gt;&lt;tr&gt;&lt;td class="linenos"&gt;&lt;div class="linenodiv"&gt;&lt;pre&gt;&lt;span class="normal"&gt;1&lt;/span&gt;
&lt;span class="normal"&gt;2&lt;/span&gt;
&lt;span class="normal"&gt;3&lt;/span&gt;
&lt;span class="normal"&gt;4&lt;/span&gt;
&lt;span class="normal"&gt;5&lt;/span&gt;
&lt;span class="normal"&gt;6&lt;/span&gt;
&lt;span class="normal"&gt;7&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/td&gt;&lt;td class="code"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;+-----+-----+.....+
| A  B|B    |     .
|A A B| B   |     .
|AAA B|B    |     .
|A A B| B   |     .
|A A B|B    |     .
+-----+-----+.....+
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;p&gt;The left column is completely full, so I don&amp;#8217;t need to buffer it any more.  The next character wants to draw in the last &lt;em&gt;partially full&lt;/em&gt; column, which here is the one containing the B; it&amp;#8217;ll also want an empty right column to overflow into if&amp;nbsp;necessary.&lt;/p&gt;
&lt;table class="highlighttable"&gt;&lt;tr&gt;&lt;td class="linenos"&gt;&lt;div class="linenodiv"&gt;&lt;pre&gt;&lt;span class="normal"&gt; 1&lt;/span&gt;
&lt;span class="normal"&gt; 2&lt;/span&gt;
&lt;span class="normal"&gt; 3&lt;/span&gt;
&lt;span class="normal"&gt; 4&lt;/span&gt;
&lt;span class="normal"&gt; 5&lt;/span&gt;
&lt;span class="normal"&gt; 6&lt;/span&gt;
&lt;span class="normal"&gt; 7&lt;/span&gt;
&lt;span class="normal"&gt; 8&lt;/span&gt;
&lt;span class="normal"&gt; 9&lt;/span&gt;
&lt;span class="normal"&gt;10&lt;/span&gt;
&lt;span class="normal"&gt;11&lt;/span&gt;
&lt;span class="normal"&gt;12&lt;/span&gt;
&lt;span class="normal"&gt;13&lt;/span&gt;
&lt;span class="normal"&gt;14&lt;/span&gt;
&lt;span class="normal"&gt;15&lt;/span&gt;
&lt;span class="normal"&gt;16&lt;/span&gt;
&lt;span class="normal"&gt;17&lt;/span&gt;
&lt;span class="normal"&gt;18&lt;/span&gt;
&lt;span class="normal"&gt;19&lt;/span&gt;
&lt;span class="normal"&gt;20&lt;/span&gt;
&lt;span class="normal"&gt;21&lt;/span&gt;
&lt;span class="normal"&gt;22&lt;/span&gt;
&lt;span class="normal"&gt;23&lt;/span&gt;
&lt;span class="normal"&gt;24&lt;/span&gt;
&lt;span class="normal"&gt;25&lt;/span&gt;
&lt;span class="normal"&gt;26&lt;/span&gt;
&lt;span class="normal"&gt;27&lt;/span&gt;
&lt;span class="normal"&gt;28&lt;/span&gt;
&lt;span class="normal"&gt;29&lt;/span&gt;
&lt;span class="normal"&gt;30&lt;/span&gt;
&lt;span class="normal"&gt;31&lt;/span&gt;
&lt;span class="normal"&gt;32&lt;/span&gt;
&lt;span class="normal"&gt;33&lt;/span&gt;
&lt;span class="normal"&gt;34&lt;/span&gt;
&lt;span class="normal"&gt;35&lt;/span&gt;
&lt;span class="normal"&gt;36&lt;/span&gt;
&lt;span class="normal"&gt;37&lt;/span&gt;
&lt;span class="normal"&gt;38&lt;/span&gt;
&lt;span class="normal"&gt;39&lt;/span&gt;
&lt;span class="normal"&gt;40&lt;/span&gt;
&lt;span class="normal"&gt;41&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/td&gt;&lt;td class="code"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;    &lt;span class="c1"&gt;; Increment the pixel offset and deal with overflow&lt;/span&gt;
    &lt;span class="ow"&gt;add&lt;/span&gt; &lt;span class="nb"&gt;a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;b&lt;/span&gt;                    &lt;span class="c1"&gt;; a &amp;lt;- new x offset&lt;/span&gt;
    &lt;span class="c1"&gt;; Regardless of whether this glyph overflowed, the VRAM&lt;/span&gt;
    &lt;span class="c1"&gt;; pointer was left at the beginning of the next (empty)&lt;/span&gt;
    &lt;span class="c1"&gt;; column, and it needs rewinding to the right column&lt;/span&gt;
    &lt;span class="ow"&gt;ld&lt;/span&gt; &lt;span class="nb"&gt;bc&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;-&lt;/span&gt;&lt;span class="nv"&gt;32&lt;/span&gt;                  &lt;span class="c1"&gt;; move the VRAM pointer back...&lt;/span&gt;
    &lt;span class="ow"&gt;add&lt;/span&gt; &lt;span class="nb"&gt;hl&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;bc&lt;/span&gt;                  &lt;span class="c1"&gt;; ...to the start of the char&lt;/span&gt;
    &lt;span class="ow"&gt;cp&lt;/span&gt; &lt;span class="nb"&gt;a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;8&lt;/span&gt;
    &lt;span class="ow"&gt;jr&lt;/span&gt; &lt;span class="nv"&gt;nc&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="err"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;wrap_to_next_char&lt;/span&gt;
    &lt;span class="c1"&gt;; The new offset is less than 8, so this character didn&amp;#39;t&lt;/span&gt;
    &lt;span class="c1"&gt;; actually draw anything in the right column.  Move the&lt;/span&gt;
    &lt;span class="c1"&gt;; VRAM pointer back a second time, to the left column,&lt;/span&gt;
    &lt;span class="c1"&gt;; which still has space left&lt;/span&gt;
    &lt;span class="ow"&gt;add&lt;/span&gt; &lt;span class="nb"&gt;hl&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;bc&lt;/span&gt;
    &lt;span class="ow"&gt;jr&lt;/span&gt; &lt;span class="err"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;done_wrap&lt;/span&gt;
&lt;span class="nf"&gt;.wrap_to_next_char:&lt;/span&gt;
    &lt;span class="c1"&gt;; The new offset is 8 or more, so this character drew into&lt;/span&gt;
    &lt;span class="c1"&gt;; the next char.  Subtract 8, but also shift the text buffer&lt;/span&gt;
    &lt;span class="c1"&gt;; by copying all the &amp;quot;right&amp;quot; chars over the &amp;quot;left&amp;quot; chars&lt;/span&gt;
    &lt;span class="ow"&gt;sub&lt;/span&gt; &lt;span class="nb"&gt;a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;8&lt;/span&gt;                    &lt;span class="c1"&gt;; a &amp;gt;= 8: subtract char width&lt;/span&gt;
    &lt;span class="ow"&gt;push&lt;/span&gt; &lt;span class="nb"&gt;hl&lt;/span&gt;
    &lt;span class="ow"&gt;push&lt;/span&gt; &lt;span class="nb"&gt;af&lt;/span&gt;
    &lt;span class="c1"&gt;; The easy way to do this is to walk backwards through the&lt;/span&gt;
    &lt;span class="c1"&gt;; buffer.  This leaves garbage in the right column, but&lt;/span&gt;
    &lt;span class="c1"&gt;; that&amp;#39;s okay -- it gets overwritten in the next loop,&lt;/span&gt;
    &lt;span class="c1"&gt;; before the buffer is copied into VRAM.&lt;/span&gt;
    &lt;span class="ow"&gt;ld&lt;/span&gt; &lt;span class="nb"&gt;hl&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;text_buffer&lt;/span&gt; &lt;span class="p"&gt;+&lt;/span&gt; &lt;span class="mh"&gt;$40&lt;/span&gt; &lt;span class="p"&gt;-&lt;/span&gt; &lt;span class="nv"&gt;1&lt;/span&gt;
    &lt;span class="ow"&gt;ld&lt;/span&gt; &lt;span class="nb"&gt;c&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mh"&gt;$20&lt;/span&gt;
&lt;span class="nf"&gt;.shift_buffer:&lt;/span&gt;
    &lt;span class="ow"&gt;ld&lt;/span&gt; &lt;span class="nb"&gt;a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;hl&lt;/span&gt;&lt;span class="p"&gt;-]&lt;/span&gt;
    &lt;span class="ow"&gt;ld&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;hl&lt;/span&gt;&lt;span class="p"&gt;-],&lt;/span&gt; &lt;span class="nb"&gt;a&lt;/span&gt;
    &lt;span class="ow"&gt;dec&lt;/span&gt; &lt;span class="nb"&gt;c&lt;/span&gt;
    &lt;span class="ow"&gt;jr&lt;/span&gt; &lt;span class="nv"&gt;nz&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="err"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;shift_buffer&lt;/span&gt;
    &lt;span class="ow"&gt;pop&lt;/span&gt; &lt;span class="nb"&gt;af&lt;/span&gt;
    &lt;span class="ow"&gt;pop&lt;/span&gt; &lt;span class="nb"&gt;hl&lt;/span&gt;
&lt;span class="nf"&gt;.done_wrap:&lt;/span&gt;
    &lt;span class="ow"&gt;ld&lt;/span&gt; &lt;span class="nb"&gt;b&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;a&lt;/span&gt;                     &lt;span class="c1"&gt;; either way, store into b&lt;/span&gt;

    &lt;span class="c1"&gt;; Loop&lt;/span&gt;
    &lt;span class="ow"&gt;pop&lt;/span&gt; &lt;span class="nb"&gt;de&lt;/span&gt;                      &lt;span class="c1"&gt;; pop text pointer&lt;/span&gt;
    &lt;span class="ow"&gt;jp&lt;/span&gt; &lt;span class="err"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;next_letter&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;p&gt;And the test&amp;nbsp;run:&lt;/p&gt;
&lt;div class="prose-full-illustration"&gt;
&lt;img src="https://eev.ee/media/cheezball/06h-text-attempt-2.png" alt="Screenshot of Anise, with a black dialogue box that says: ABABAAA"&gt;
&lt;/div&gt;

&lt;p&gt;Hey hey,&amp;nbsp;success!&lt;/p&gt;
&lt;h2 id="quick-diversion-anise-corruption"&gt;&lt;a class="toclink" href="#quick-diversion-anise-corruption"&gt;Quick diversion: Anise&amp;nbsp;corruption&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;I didn&amp;#8217;t mention it above because I didn&amp;#8217;t actually use it yet, but while doing that second pass, I split the button-polling code out into its own function, &lt;code&gt;read_input&lt;/code&gt;.  I thought I might need it in dialogue as well (which has its own vblank loop and thus needs to do its own polling), but I didn&amp;#8217;t get that far yet, so it&amp;#8217;s still only called from the main&amp;nbsp;loop.&lt;/p&gt;
&lt;p&gt;While testing out the dialogue, I notice a teeny tiny&amp;nbsp;problem.&lt;/p&gt;
&lt;div class="prose-full-illustration"&gt;
&lt;img src="https://eev.ee/media/cheezball/06i-anise-corruption.png" alt="A screenshot similar to the above, but with some mild graphical corruption on Anise"&gt;
&lt;/div&gt;

&lt;p&gt;Well, yes, obviously there&amp;#8217;s the problem of the textbox drawing &lt;em&gt;underneath&lt;/em&gt; the player.  Which is mostly a problem because the textbox doesn&amp;#8217;t go away, ever.  I&amp;#8217;ll worry about that&amp;nbsp;later.&lt;/p&gt;
&lt;p&gt;The &lt;em&gt;other&lt;/em&gt; problem is that Anise&amp;#8217;s sprite is corrupt.  &lt;em&gt;Again.&lt;/em&gt;&amp;nbsp;Argh!&lt;/p&gt;
&lt;p&gt;A little investigation suggests that, once again, I&amp;#8217;m blowing my vblank budget.  But this time, it&amp;#8217;s a little more reasonable.  Remember, I&amp;#8217;m overwriting Anise&amp;#8217;s sprite &lt;em&gt;after&lt;/em&gt; handling movement.  That means I do a bunch of logic &lt;em&gt;followed by&lt;/em&gt; writing to char data.  No wonder there&amp;#8217;s a problem.  I must&amp;#8217;ve just slightly overrun vblank when I split out &lt;code&gt;read_input&lt;/code&gt; (or checked for the dialogue button press in the first place?), since &lt;code&gt;call&lt;/code&gt; has a teeny tiny bit of&amp;nbsp;overhead.&lt;/p&gt;
&lt;p&gt;That approach is a little inconsistent, as well.  Remember how I handle &lt;span class="caps"&gt;OAM&lt;/span&gt;: I write to a buffer, which is then copied to real &lt;span class="caps"&gt;OAM&lt;/span&gt; during the next vblank.  But I&amp;#8217;m updating the sprite &lt;em&gt;immediately&lt;/em&gt;.  That means when Anise turns, the sprite updates on the very next frame, but the movement isn&amp;#8217;t visible until the frame &lt;em&gt;after&lt;/em&gt; that.&amp;nbsp;Whoops.&lt;/p&gt;
&lt;p&gt;So, a buffer!  I could make this into a more general mechanism later, but for now I only care about fixing Anise.  I can revisit this when I have, uh, a second&amp;nbsp;sprite.&lt;/p&gt;
&lt;table class="highlighttable"&gt;&lt;tr&gt;&lt;td class="linenos"&gt;&lt;div class="linenodiv"&gt;&lt;pre&gt;&lt;span class="normal"&gt;1&lt;/span&gt;
&lt;span class="normal"&gt;2&lt;/span&gt;
&lt;span class="normal"&gt;3&lt;/span&gt;
&lt;span class="normal"&gt;4&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/td&gt;&lt;td class="code"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="c1"&gt;; in ram somewhere&lt;/span&gt;

&lt;span class="nf"&gt;anise_sprites_address:&lt;/span&gt;
    &lt;span class="k"&gt;dw&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;p&gt;Now, Anise is composed of three objects, which is six chars, which is 96 bytes.  The fastest way to copy bytes by hand is something like&amp;nbsp;this:&lt;/p&gt;
&lt;table class="highlighttable"&gt;&lt;tr&gt;&lt;td class="linenos"&gt;&lt;div class="linenodiv"&gt;&lt;pre&gt;&lt;span class="normal"&gt;1&lt;/span&gt;
&lt;span class="normal"&gt;2&lt;/span&gt;
&lt;span class="normal"&gt;3&lt;/span&gt;
&lt;span class="normal"&gt;4&lt;/span&gt;
&lt;span class="normal"&gt;5&lt;/span&gt;
&lt;span class="normal"&gt;6&lt;/span&gt;
&lt;span class="normal"&gt;7&lt;/span&gt;
&lt;span class="normal"&gt;8&lt;/span&gt;
&lt;span class="normal"&gt;9&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/td&gt;&lt;td class="code"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;    &lt;span class="ow"&gt;ld&lt;/span&gt; &lt;span class="nb"&gt;hl&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;source&lt;/span&gt;
    &lt;span class="ow"&gt;ld&lt;/span&gt; &lt;span class="nb"&gt;de&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;destination&lt;/span&gt;
    &lt;span class="ow"&gt;ld&lt;/span&gt; &lt;span class="nb"&gt;c&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;96&lt;/span&gt;
&lt;span class="nf"&gt;.loop:&lt;/span&gt;
    &lt;span class="ow"&gt;ld&lt;/span&gt; &lt;span class="nb"&gt;a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;hl&lt;/span&gt;&lt;span class="p"&gt;+]&lt;/span&gt;
    &lt;span class="ow"&gt;ld&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;de&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="nb"&gt;a&lt;/span&gt;
    &lt;span class="ow"&gt;inc&lt;/span&gt; &lt;span class="nb"&gt;de&lt;/span&gt;
    &lt;span class="ow"&gt;dec&lt;/span&gt; &lt;span class="nb"&gt;c&lt;/span&gt;
    &lt;span class="ow"&gt;jr&lt;/span&gt; &lt;span class="nv"&gt;nz&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="err"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;loop&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;p&gt;Each iteration of the loop copies 1 byte and takes 7 cycles.  (It&amp;#8217;s possible to shave a couple cycles off in some specific cases, and unrolling would save some time, but let&amp;#8217;s stay general for now.)  That&amp;#8217;s 672 cycles, plus 10 for the setup, minus one on the final &lt;code&gt;jr&lt;/code&gt;, for 681 total.  But vblank only lasts 1140 cycles!  That&amp;#8217;s more than half the budget blown for updating a &lt;em&gt;single entity&lt;/em&gt;.  This can&amp;#8217;t possibly&amp;nbsp;work.&lt;/p&gt;
&lt;p&gt;Enter a feature exclusive to the Game Boy Color: &lt;span class="caps"&gt;GDMA&lt;/span&gt;, or &lt;em&gt;general&lt;/em&gt; &lt;span class="caps"&gt;DMA&lt;/span&gt;.  This is similar to &lt;span class="caps"&gt;OAM&lt;/span&gt; &lt;span class="caps"&gt;DMA&lt;/span&gt;, except that it can copy (nearly) anything to anywhere.  Also (unlike &lt;span class="caps"&gt;OAM&lt;/span&gt; &lt;span class="caps"&gt;DMA&lt;/span&gt;), the &lt;span class="caps"&gt;CPU&lt;/span&gt; &lt;em&gt;pauses&lt;/em&gt; while the copy is taking place, so there&amp;#8217;s no need to carefully time a busy loop.  It&amp;#8217;s configured by writing to five control registers (which takes 5 cycles each), and then it copies &lt;em&gt;two bytes per cycle&lt;/em&gt;, for a total of 73 cycles.  That&amp;#8217;s &lt;strong&gt;9.3 times faster&lt;/strong&gt;.  Seems worth a&amp;nbsp;try.&lt;/p&gt;
&lt;p&gt;(Note that I&amp;#8217;m not using double-speed &lt;span class="caps"&gt;CPU&lt;/span&gt; mode yet, as an incentive to not blow my &lt;span class="caps"&gt;CPU&lt;/span&gt; budget early on.  Turning that on would halve the time taken by the manual loop, but wouldn&amp;#8217;t affect &lt;span class="caps"&gt;GDMA&lt;/span&gt;.)&lt;/p&gt;
&lt;p&gt;&lt;span class="caps"&gt;GDMA&lt;/span&gt; has a couple restrictions: most notably, it can only copy multiples of 16 bytes, and only to/from addresses that are aligned to 16 bytes.  But each char is 16 bytes, so that works out just&amp;nbsp;fine.&lt;/p&gt;
&lt;p&gt;The five &lt;span class="caps"&gt;GDMA&lt;/span&gt; registers are, alas, simply named 1 through 5.  The first two are the source address; the next two are the destination address; the last is the amount to copy.  Or, well, it&amp;#8217;s the amount to copy, divided by 16, minus 1.  (The high bit is reserved for turning on a different kind of &lt;span class="caps"&gt;DMA&lt;/span&gt; that operates a bit at a time during hblanks.)  Writing to the last register triggers the&amp;nbsp;copy.&lt;/p&gt;
&lt;p&gt;Plugging in this buffer is easy enough,&amp;nbsp;then:&lt;/p&gt;
&lt;table class="highlighttable"&gt;&lt;tr&gt;&lt;td class="linenos"&gt;&lt;div class="linenodiv"&gt;&lt;pre&gt;&lt;span class="normal"&gt; 1&lt;/span&gt;
&lt;span class="normal"&gt; 2&lt;/span&gt;
&lt;span class="normal"&gt; 3&lt;/span&gt;
&lt;span class="normal"&gt; 4&lt;/span&gt;
&lt;span class="normal"&gt; 5&lt;/span&gt;
&lt;span class="normal"&gt; 6&lt;/span&gt;
&lt;span class="normal"&gt; 7&lt;/span&gt;
&lt;span class="normal"&gt; 8&lt;/span&gt;
&lt;span class="normal"&gt; 9&lt;/span&gt;
&lt;span class="normal"&gt;10&lt;/span&gt;
&lt;span class="normal"&gt;11&lt;/span&gt;
&lt;span class="normal"&gt;12&lt;/span&gt;
&lt;span class="normal"&gt;13&lt;/span&gt;
&lt;span class="normal"&gt;14&lt;/span&gt;
&lt;span class="normal"&gt;15&lt;/span&gt;
&lt;span class="normal"&gt;16&lt;/span&gt;
&lt;span class="normal"&gt;17&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/td&gt;&lt;td class="code"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;    &lt;span class="c1"&gt;; Update Anise&amp;#39;s current sprite.  Use DMA here because...&lt;/span&gt;
    &lt;span class="c1"&gt;; well, geez, it&amp;#39;s too slow otherwise.&lt;/span&gt;
    &lt;span class="ow"&gt;ld&lt;/span&gt; &lt;span class="nb"&gt;hl&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;anise_sprites_address&lt;/span&gt;
    &lt;span class="ow"&gt;ld&lt;/span&gt; &lt;span class="nb"&gt;a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;hl&lt;/span&gt;&lt;span class="p"&gt;+]&lt;/span&gt;
    &lt;span class="ow"&gt;ld&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;rHDMA1&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="nb"&gt;a&lt;/span&gt;
    &lt;span class="ow"&gt;ld&lt;/span&gt; &lt;span class="nb"&gt;a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;hl&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="ow"&gt;ld&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;rHDMA2&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="nb"&gt;a&lt;/span&gt;
    &lt;span class="c1"&gt;; I want to write to $8000 which is where Anise&amp;#39;s sprite is&lt;/span&gt;
    &lt;span class="c1"&gt;; hardcoded to live, and the top three bits are ignored so&lt;/span&gt;
    &lt;span class="c1"&gt;; that the destination is always in VRAM, so $0000 works too&lt;/span&gt;
    &lt;span class="ow"&gt;ld&lt;/span&gt; &lt;span class="nb"&gt;a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;HIGH&lt;/span&gt;&lt;span class="err"&gt;(&lt;/span&gt;&lt;span class="mh"&gt;$0000&lt;/span&gt;&lt;span class="err"&gt;)&lt;/span&gt;
    &lt;span class="ow"&gt;ld&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;rHDMA3&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="nb"&gt;a&lt;/span&gt;
    &lt;span class="ow"&gt;ld&lt;/span&gt; &lt;span class="nb"&gt;a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;LOW&lt;/span&gt;&lt;span class="err"&gt;(&lt;/span&gt;&lt;span class="mh"&gt;$0000&lt;/span&gt;&lt;span class="err"&gt;)&lt;/span&gt;
    &lt;span class="ow"&gt;ld&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;rHDMA4&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="nb"&gt;a&lt;/span&gt;
    &lt;span class="c1"&gt;; And copy!&lt;/span&gt;
    &lt;span class="ow"&gt;ld&lt;/span&gt; &lt;span class="nb"&gt;a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="err"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;32&lt;/span&gt; &lt;span class="err"&gt;*&lt;/span&gt; &lt;span class="nv"&gt;3&lt;/span&gt;&lt;span class="err"&gt;)&lt;/span&gt; &lt;span class="err"&gt;/&lt;/span&gt; &lt;span class="nv"&gt;16&lt;/span&gt; &lt;span class="p"&gt;-&lt;/span&gt; &lt;span class="nv"&gt;1&lt;/span&gt;
    &lt;span class="ow"&gt;ld&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;rHDMA5&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="nb"&gt;a&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;p&gt;Finally, instead of actually overwriting Anise&amp;#8217;s sprite, I write the address of the new sprite into the&amp;nbsp;buffer:&lt;/p&gt;
&lt;table class="highlighttable"&gt;&lt;tr&gt;&lt;td class="linenos"&gt;&lt;div class="linenodiv"&gt;&lt;pre&gt;&lt;span class="normal"&gt;1&lt;/span&gt;
&lt;span class="normal"&gt;2&lt;/span&gt;
&lt;span class="normal"&gt;3&lt;/span&gt;
&lt;span class="normal"&gt;4&lt;/span&gt;
&lt;span class="normal"&gt;5&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/td&gt;&lt;td class="code"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;    &lt;span class="c1"&gt;; Store the new sprite address, to be updated during vblank&lt;/span&gt;
    &lt;span class="ow"&gt;ld&lt;/span&gt; &lt;span class="nb"&gt;a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;h&lt;/span&gt;
    &lt;span class="ow"&gt;ld&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;anise_sprites_address&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="nb"&gt;a&lt;/span&gt;
    &lt;span class="ow"&gt;ld&lt;/span&gt; &lt;span class="nb"&gt;a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;l&lt;/span&gt;
    &lt;span class="ow"&gt;ld&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;anise_sprites_address&lt;/span&gt; &lt;span class="p"&gt;+&lt;/span&gt; &lt;span class="nv"&gt;1&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="nb"&gt;a&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;p&gt;And done!  Now I can walk around just fine.  It looks basically like the screenshot from the previous section, so I don&amp;#8217;t think you need a new&amp;nbsp;one.&lt;/p&gt;
&lt;p&gt;Note that this copy will &lt;em&gt;always&lt;/em&gt; happen, since there&amp;#8217;s no condition for skipping it when there&amp;#8217;s nothing to do.  That&amp;#8217;s fine for now; later I&amp;#8217;ll turn this into a list, and after copying everything I&amp;#8217;ll simply clear the&amp;nbsp;list.&lt;/p&gt;
&lt;p&gt;Crisis averted, or at least deferred until later.  Back to the&amp;nbsp;dialogue!&lt;/p&gt;
&lt;h2 id="interlude-a-font"&gt;&lt;a class="toclink" href="#interlude-a-font"&gt;Interlude: A&amp;nbsp;font&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Writing out the glyphs &lt;em&gt;by hand&lt;/em&gt; is not going to cut it.  It was fairly annoying for two letters, let alone an entire&amp;nbsp;alphabet.&lt;/p&gt;
&lt;p&gt;Nothing about this part was &lt;em&gt;especially&lt;/em&gt; interesting.  I used LÖVE&amp;#8217;s font format, which puts all glyphs in a single horizontal strip.  The color of the top-left pixel is used as a sentinel; any pixel in the top row that&amp;#8217;s the same color indicates the start of a new&amp;nbsp;glyph.&lt;/p&gt;
&lt;p&gt;(I note that LÖVE actually recommends &lt;em&gt;against&lt;/em&gt; using this format, but the alternatives are more complicated and require platform-specific software — whereas I can slop this format together in any image editor without much&amp;nbsp;trouble.)&lt;/p&gt;
&lt;p&gt;I then turned this into Game Boy tiles much the same way as with the sprite loader, except with the extra logic to split on the sentinel pixels and pad each glyph to eight pixels wide.  I won&amp;#8217;t reproduce the whole script here, but it&amp;#8217;s &lt;a href="https://github.com/eevee/anise-cheezball-rising/blob/5b2fc1218d50ccc03151bcaacd0feb607fe22908/util/font-to-tiles.py"&gt;on GitHub&lt;/a&gt; if you want to see&amp;nbsp;it.&lt;/p&gt;
&lt;p&gt;The font itself is, well, a font?  I initially tried to give it &lt;a href="https://github.com/eevee/anise-cheezball-rising/blob/5b2fc1218d50ccc03151bcaacd0feb607fe22908/data/font.png"&gt;a little personality&lt;/a&gt;, but that made some of the characters weirdly wide and was a bit hard to read, so I revisited it and ended up with&amp;nbsp;this:&lt;/p&gt;
&lt;div class="prose-full-illustration"&gt;
&lt;img src="https://eev.ee/media/cheezball/06j-font.png" alt="Pixel font covering all of ASCII"&gt;
&lt;/div&gt;

&lt;p&gt;I like it, at least!  The characters all have shadows built right in, and you can see at the end that I was starting to play with some non-&lt;span class="caps"&gt;ASCII&lt;/span&gt; characters.  Because I can do&amp;nbsp;that!&lt;/p&gt;
&lt;h2 id="third-pass"&gt;&lt;a class="toclink" href="#third-pass"&gt;Third&amp;nbsp;pass&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;One major obstacle remains: I can only have one line of text right now, when there&amp;#8217;s plenty of space for&amp;nbsp;two.&lt;/p&gt;
&lt;p&gt;The obvious first thing I need to do is alter the dialogue box&amp;#8217;s char map.  It currently has a whole char&amp;#8217;s worth of padding on every side.  What a waste.  I want this&amp;nbsp;instead:&lt;/p&gt;
&lt;table class="highlighttable"&gt;&lt;tr&gt;&lt;td class="linenos"&gt;&lt;div class="linenodiv"&gt;&lt;pre&gt;&lt;span class="normal"&gt;1&lt;/span&gt;
&lt;span class="normal"&gt;2&lt;/span&gt;
&lt;span class="normal"&gt;3&lt;/span&gt;
&lt;span class="normal"&gt;4&lt;/span&gt;
&lt;span class="normal"&gt;5&lt;/span&gt;
&lt;span class="normal"&gt;6&lt;/span&gt;
&lt;span class="normal"&gt;7&lt;/span&gt;
&lt;span class="normal"&gt;8&lt;/span&gt;
&lt;span class="normal"&gt;9&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/td&gt;&lt;td class="code"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="nb"&gt;+--+--+--+--+--+--+--+--+--+--+--+--+---+&lt;/span&gt;&lt;span class="c"&gt;&lt;/span&gt;
&lt;span class="c"&gt;|80|82|84|86|88|8a|8c|8e|90|92|94|96|&lt;/span&gt;&lt;span class="nt"&gt;...&lt;/span&gt;&lt;span class="c"&gt;|&lt;/span&gt;
&lt;span class="nb"&gt;+--+--+--+--+--+--+--+--+--+--+--+--+---+&lt;/span&gt;&lt;span class="c"&gt;&lt;/span&gt;
&lt;span class="c"&gt;|81|83|85|87|89|8b|8d|8f|91|93|95|97|&lt;/span&gt;&lt;span class="nt"&gt;...&lt;/span&gt;&lt;span class="c"&gt;|&lt;/span&gt;
&lt;span class="nb"&gt;+--+--+--+--+--+--+--+--+--+--+--+--+---+&lt;/span&gt;&lt;span class="c"&gt;&lt;/span&gt;
&lt;span class="c"&gt;|a8|aa|ac|ae|b0|b2|b4|b6|b8|ba|bc|be|&lt;/span&gt;&lt;span class="nt"&gt;...&lt;/span&gt;&lt;span class="c"&gt;|&lt;/span&gt;
&lt;span class="nb"&gt;+--+--+--+--+--+--+--+--+--+--+--+--+---+&lt;/span&gt;&lt;span class="c"&gt;&lt;/span&gt;
&lt;span class="c"&gt;|a9|ab|ad|af|b1|b3|b5|b7|b9|bb|bd|bf|&lt;/span&gt;&lt;span class="nt"&gt;...&lt;/span&gt;&lt;span class="c"&gt;|&lt;/span&gt;
&lt;span class="nb"&gt;+--+--+--+--+--+--+--+--+--+--+--+--+---+&lt;/span&gt;&lt;span class="c"&gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;p&gt;The second row begins with char $a8 because that&amp;#8217;s $80 +&amp;nbsp;40.&lt;/p&gt;
&lt;p&gt;Obviously I&amp;#8217;ll need to change the setup code to make the above pattern.  But &lt;em&gt;while I&amp;#8217;m in here&lt;/em&gt;&amp;#8230;  remember, the setup code is the only remaining place that disables the &lt;span class="caps"&gt;LCD&lt;/span&gt; to do its work.  Can I do everything within vblank&amp;nbsp;instead?&lt;/p&gt;
&lt;p&gt;I&amp;#8217;m actually not sure, but there&amp;#8217;s an easy way to reduce the &lt;span class="caps"&gt;CPU&lt;/span&gt; cost.  Instead of setting up the whole dialogue box at once, I can do it &lt;em&gt;one row at a time&lt;/em&gt;, starting from the bottom.  That will cut the vblank pressure by a factor of four, &lt;em&gt;and&lt;/em&gt; it&amp;#8217;ll create a cool slide-up effect when the dialogue box&amp;nbsp;opens!&lt;/p&gt;
&lt;p&gt;Let&amp;#8217;s give it a try.  I&amp;#8217;ll move the real code into a function, since it&amp;#8217;ll run multiple times now.  I&amp;#8217;ll also introduce a few constants, since I&amp;#8217;m getting tired of all the magic numbers&amp;nbsp;everywhere.&lt;/p&gt;
&lt;table class="highlighttable"&gt;&lt;tr&gt;&lt;td class="linenos"&gt;&lt;div class="linenodiv"&gt;&lt;pre&gt;&lt;span class="normal"&gt; 1&lt;/span&gt;
&lt;span class="normal"&gt; 2&lt;/span&gt;
&lt;span class="normal"&gt; 3&lt;/span&gt;
&lt;span class="normal"&gt; 4&lt;/span&gt;
&lt;span class="normal"&gt; 5&lt;/span&gt;
&lt;span class="normal"&gt; 6&lt;/span&gt;
&lt;span class="normal"&gt; 7&lt;/span&gt;
&lt;span class="normal"&gt; 8&lt;/span&gt;
&lt;span class="normal"&gt; 9&lt;/span&gt;
&lt;span class="normal"&gt;10&lt;/span&gt;
&lt;span class="normal"&gt;11&lt;/span&gt;
&lt;span class="normal"&gt;12&lt;/span&gt;
&lt;span class="normal"&gt;13&lt;/span&gt;
&lt;span class="normal"&gt;14&lt;/span&gt;
&lt;span class="normal"&gt;15&lt;/span&gt;
&lt;span class="normal"&gt;16&lt;/span&gt;
&lt;span class="normal"&gt;17&lt;/span&gt;
&lt;span class="normal"&gt;18&lt;/span&gt;
&lt;span class="normal"&gt;19&lt;/span&gt;
&lt;span class="normal"&gt;20&lt;/span&gt;
&lt;span class="normal"&gt;21&lt;/span&gt;
&lt;span class="normal"&gt;22&lt;/span&gt;
&lt;span class="normal"&gt;23&lt;/span&gt;
&lt;span class="normal"&gt;24&lt;/span&gt;
&lt;span class="normal"&gt;25&lt;/span&gt;
&lt;span class="normal"&gt;26&lt;/span&gt;
&lt;span class="normal"&gt;27&lt;/span&gt;
&lt;span class="normal"&gt;28&lt;/span&gt;
&lt;span class="normal"&gt;29&lt;/span&gt;
&lt;span class="normal"&gt;30&lt;/span&gt;
&lt;span class="normal"&gt;31&lt;/span&gt;
&lt;span class="normal"&gt;32&lt;/span&gt;
&lt;span class="normal"&gt;33&lt;/span&gt;
&lt;span class="normal"&gt;34&lt;/span&gt;
&lt;span class="normal"&gt;35&lt;/span&gt;
&lt;span class="normal"&gt;36&lt;/span&gt;
&lt;span class="normal"&gt;37&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/td&gt;&lt;td class="code"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="nf"&gt;SCREEN_WIDTH_TILES&lt;/span&gt; &lt;span class="k"&gt;EQU&lt;/span&gt; &lt;span class="mi"&gt;20&lt;/span&gt;
&lt;span class="nf"&gt;CANVAS_WIDTH_TILES&lt;/span&gt; &lt;span class="k"&gt;EQU&lt;/span&gt; &lt;span class="mi"&gt;32&lt;/span&gt;
&lt;span class="nf"&gt;SCREEN_HEIGHT_TILES&lt;/span&gt; &lt;span class="k"&gt;EQU&lt;/span&gt; &lt;span class="mi"&gt;18&lt;/span&gt;
&lt;span class="nf"&gt;CANVAS_HEIGHT_TILES&lt;/span&gt; &lt;span class="k"&gt;EQU&lt;/span&gt; &lt;span class="mi"&gt;32&lt;/span&gt;
&lt;span class="nf"&gt;BYTES_PER_TILE&lt;/span&gt; &lt;span class="k"&gt;EQU&lt;/span&gt; &lt;span class="mi"&gt;16&lt;/span&gt;
&lt;span class="nf"&gt;TEXT_START_TILE_1&lt;/span&gt; &lt;span class="k"&gt;EQU&lt;/span&gt; &lt;span class="mi"&gt;128&lt;/span&gt;
&lt;span class="nf"&gt;TEXT_START_TILE_2&lt;/span&gt; &lt;span class="k"&gt;EQU&lt;/span&gt; &lt;span class="err"&gt;TEXT_START_TILE_&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="err"&gt;+&lt;/span&gt; &lt;span class="err"&gt;SCREEN_WIDTH_TILES&lt;/span&gt; &lt;span class="err"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;

&lt;span class="c1"&gt;; Fill a row in the tilemap in a way that&amp;#39;s helpful to dialogue.&lt;/span&gt;
&lt;span class="c1"&gt;; hl: where to start filling&lt;/span&gt;
&lt;span class="c1"&gt;; b: tile to start with&lt;/span&gt;
&lt;span class="nf"&gt;fill_tilemap_row:&lt;/span&gt;
    &lt;span class="c1"&gt;; Populate bank 0, the tile proper&lt;/span&gt;
    &lt;span class="ow"&gt;xor&lt;/span&gt; &lt;span class="nb"&gt;a&lt;/span&gt;
    &lt;span class="ow"&gt;ldh&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;rVBK&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="nb"&gt;a&lt;/span&gt;

    &lt;span class="ow"&gt;ld&lt;/span&gt; &lt;span class="nb"&gt;c&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;SCREEN_WIDTH_TILES&lt;/span&gt;
    &lt;span class="ow"&gt;ld&lt;/span&gt; &lt;span class="nb"&gt;a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;b&lt;/span&gt;
&lt;span class="nf"&gt;.loop0:&lt;/span&gt;
    &lt;span class="ow"&gt;ld&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;hl&lt;/span&gt;&lt;span class="p"&gt;+],&lt;/span&gt; &lt;span class="nb"&gt;a&lt;/span&gt;
    &lt;span class="c1"&gt;; Each successive tile in a row increases by 2!&lt;/span&gt;
    &lt;span class="ow"&gt;add&lt;/span&gt; &lt;span class="nb"&gt;a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;2&lt;/span&gt;
    &lt;span class="ow"&gt;dec&lt;/span&gt; &lt;span class="nb"&gt;c&lt;/span&gt;
    &lt;span class="ow"&gt;jr&lt;/span&gt; &lt;span class="nv"&gt;nz&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="err"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;loop0&lt;/span&gt;

    &lt;span class="c1"&gt;; Populate bank 1, the bank and palette&lt;/span&gt;
    &lt;span class="ow"&gt;ld&lt;/span&gt; &lt;span class="nb"&gt;a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;1&lt;/span&gt;
    &lt;span class="ow"&gt;ldh&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;rVBK&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="nb"&gt;a&lt;/span&gt;
    &lt;span class="ow"&gt;ld&lt;/span&gt; &lt;span class="nb"&gt;a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mb"&gt;%00001111&lt;/span&gt;  &lt;span class="c1"&gt;; bank 1, palette 7&lt;/span&gt;
    &lt;span class="ow"&gt;ld&lt;/span&gt; &lt;span class="nb"&gt;c&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;SCREEN_WIDTH_TILES&lt;/span&gt;
    &lt;span class="ow"&gt;dec&lt;/span&gt; &lt;span class="nb"&gt;hl&lt;/span&gt;
&lt;span class="nf"&gt;.loop1:&lt;/span&gt;
    &lt;span class="ow"&gt;ld&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;hl&lt;/span&gt;&lt;span class="p"&gt;-],&lt;/span&gt; &lt;span class="nb"&gt;a&lt;/span&gt;
    &lt;span class="ow"&gt;dec&lt;/span&gt; &lt;span class="nb"&gt;c&lt;/span&gt;
    &lt;span class="ow"&gt;jr&lt;/span&gt; &lt;span class="nv"&gt;nz&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="err"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;loop1&lt;/span&gt;

    &lt;span class="ow"&gt;ret&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;p&gt;Now replace the setup code with four calls to this function, waiting for vblank between successive&amp;nbsp;calls.&lt;/p&gt;
&lt;table class="highlighttable"&gt;&lt;tr&gt;&lt;td class="linenos"&gt;&lt;div class="linenodiv"&gt;&lt;pre&gt;&lt;span class="normal"&gt; 1&lt;/span&gt;
&lt;span class="normal"&gt; 2&lt;/span&gt;
&lt;span class="normal"&gt; 3&lt;/span&gt;
&lt;span class="normal"&gt; 4&lt;/span&gt;
&lt;span class="normal"&gt; 5&lt;/span&gt;
&lt;span class="normal"&gt; 6&lt;/span&gt;
&lt;span class="normal"&gt; 7&lt;/span&gt;
&lt;span class="normal"&gt; 8&lt;/span&gt;
&lt;span class="normal"&gt; 9&lt;/span&gt;
&lt;span class="normal"&gt;10&lt;/span&gt;
&lt;span class="normal"&gt;11&lt;/span&gt;
&lt;span class="normal"&gt;12&lt;/span&gt;
&lt;span class="normal"&gt;13&lt;/span&gt;
&lt;span class="normal"&gt;14&lt;/span&gt;
&lt;span class="normal"&gt;15&lt;/span&gt;
&lt;span class="normal"&gt;16&lt;/span&gt;
&lt;span class="normal"&gt;17&lt;/span&gt;
&lt;span class="normal"&gt;18&lt;/span&gt;
&lt;span class="normal"&gt;19&lt;/span&gt;
&lt;span class="normal"&gt;20&lt;/span&gt;
&lt;span class="normal"&gt;21&lt;/span&gt;
&lt;span class="normal"&gt;22&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/td&gt;&lt;td class="code"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;    &lt;span class="c1"&gt;; Row 4&lt;/span&gt;
    &lt;span class="ow"&gt;ld&lt;/span&gt; &lt;span class="nb"&gt;hl&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mh"&gt;$9800&lt;/span&gt; &lt;span class="p"&gt;+&lt;/span&gt; &lt;span class="nv"&gt;CANVAS_WIDTH_TILES&lt;/span&gt; &lt;span class="err"&gt;*&lt;/span&gt; &lt;span class="err"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;SCREEN_HEIGHT_TILES&lt;/span&gt; &lt;span class="p"&gt;-&lt;/span&gt; &lt;span class="nv"&gt;1&lt;/span&gt;&lt;span class="err"&gt;)&lt;/span&gt;
    &lt;span class="ow"&gt;ld&lt;/span&gt; &lt;span class="nb"&gt;b&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;TEXT_START_TILE_2&lt;/span&gt; &lt;span class="p"&gt;+&lt;/span&gt; &lt;span class="nv"&gt;1&lt;/span&gt;
    &lt;span class="ow"&gt;call&lt;/span&gt; &lt;span class="nv"&gt;fill_tilemap_row&lt;/span&gt;

    &lt;span class="c1"&gt;; Row 3&lt;/span&gt;
    &lt;span class="ow"&gt;call&lt;/span&gt; &lt;span class="nv"&gt;wait_for_vblank&lt;/span&gt;
    &lt;span class="ow"&gt;ld&lt;/span&gt; &lt;span class="nb"&gt;hl&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mh"&gt;$9800&lt;/span&gt; &lt;span class="p"&gt;+&lt;/span&gt; &lt;span class="nv"&gt;CANVAS_WIDTH_TILES&lt;/span&gt; &lt;span class="err"&gt;*&lt;/span&gt; &lt;span class="err"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;SCREEN_HEIGHT_TILES&lt;/span&gt; &lt;span class="p"&gt;-&lt;/span&gt; &lt;span class="nv"&gt;2&lt;/span&gt;&lt;span class="err"&gt;)&lt;/span&gt;
    &lt;span class="ow"&gt;ld&lt;/span&gt; &lt;span class="nb"&gt;b&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;TEXT_START_TILE_2&lt;/span&gt;
    &lt;span class="ow"&gt;call&lt;/span&gt; &lt;span class="nv"&gt;fill_tilemap_row&lt;/span&gt;

    &lt;span class="c1"&gt;; Row 2&lt;/span&gt;
    &lt;span class="ow"&gt;call&lt;/span&gt; &lt;span class="nv"&gt;wait_for_vblank&lt;/span&gt;
    &lt;span class="ow"&gt;ld&lt;/span&gt; &lt;span class="nb"&gt;hl&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mh"&gt;$9800&lt;/span&gt; &lt;span class="p"&gt;+&lt;/span&gt; &lt;span class="nv"&gt;CANVAS_WIDTH_TILES&lt;/span&gt; &lt;span class="err"&gt;*&lt;/span&gt; &lt;span class="err"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;SCREEN_HEIGHT_TILES&lt;/span&gt; &lt;span class="p"&gt;-&lt;/span&gt; &lt;span class="nv"&gt;3&lt;/span&gt;&lt;span class="err"&gt;)&lt;/span&gt;
    &lt;span class="ow"&gt;ld&lt;/span&gt; &lt;span class="nb"&gt;b&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;TEXT_START_TILE_1&lt;/span&gt; &lt;span class="p"&gt;+&lt;/span&gt; &lt;span class="nv"&gt;1&lt;/span&gt;
    &lt;span class="ow"&gt;call&lt;/span&gt; &lt;span class="nv"&gt;fill_tilemap_row&lt;/span&gt;

    &lt;span class="c1"&gt;; Row 1&lt;/span&gt;
    &lt;span class="ow"&gt;call&lt;/span&gt; &lt;span class="nv"&gt;wait_for_vblank&lt;/span&gt;
    &lt;span class="ow"&gt;ld&lt;/span&gt; &lt;span class="nb"&gt;hl&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mh"&gt;$9800&lt;/span&gt; &lt;span class="p"&gt;+&lt;/span&gt; &lt;span class="nv"&gt;CANVAS_WIDTH_TILES&lt;/span&gt; &lt;span class="err"&gt;*&lt;/span&gt; &lt;span class="err"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;SCREEN_HEIGHT_TILES&lt;/span&gt; &lt;span class="p"&gt;-&lt;/span&gt; &lt;span class="nv"&gt;4&lt;/span&gt;&lt;span class="err"&gt;)&lt;/span&gt;
    &lt;span class="ow"&gt;ld&lt;/span&gt; &lt;span class="nb"&gt;b&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;TEXT_START_TILE_1&lt;/span&gt;
    &lt;span class="ow"&gt;call&lt;/span&gt; &lt;span class="nv"&gt;fill_tilemap_row&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;p&gt;Cool.  I have a full font now, too, so I might as well try it out with some more interesting&amp;nbsp;text.&lt;/p&gt;
&lt;table class="highlighttable"&gt;&lt;tr&gt;&lt;td class="linenos"&gt;&lt;div class="linenodiv"&gt;&lt;pre&gt;&lt;span class="normal"&gt;1&lt;/span&gt;
&lt;span class="normal"&gt;2&lt;/span&gt;
&lt;span class="normal"&gt;3&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/td&gt;&lt;td class="code"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="k"&gt;SECTION &amp;quot;Font&amp;quot;, ROMX&lt;/span&gt;
&lt;span class="nf"&gt;text:&lt;/span&gt;
    &lt;span class="k"&gt;db &amp;quot;The quick brown fox jumps over the     lazy dog&amp;#39;s back.  AOOWWRRR!!!!&amp;quot;, 0&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;p&gt;Now I just need to—  oh, hang&amp;nbsp;on.&lt;/p&gt;
&lt;div class="prose-full-illustration"&gt;
&lt;img src="https://eev.ee/media/cheezball/06k-quickbrownfox.gif" alt="Animation of the text box sliding up and scrolling out the text"&gt;
&lt;/div&gt;

&lt;p&gt;Hey, it already works!  &lt;em&gt;Magic&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;(I did also change the initial value for the x-offset to 4 rather than 0, so the text doesn&amp;#8217;t start against the left edge of the&amp;nbsp;screen.)&lt;/p&gt;
&lt;p&gt;Well.  Not &lt;em&gt;really&lt;/em&gt;.  The code I wrote doesn&amp;#8217;t actually know when to stop writing, so it continues off the end of the first line and onto the second.  You may notice the conspicuous number of extra spaces in the new&amp;nbsp;text.&lt;/p&gt;
&lt;p&gt;Still, it &lt;em&gt;looks&lt;/em&gt; right, and this was a lot of effort already, and it&amp;#8217;s not actually plugged into anything yet, so I called this a success and shelved it for now.  Quit while you&amp;#8217;re ahead,&amp;nbsp;right?&lt;/p&gt;
&lt;h2 id="future-work"&gt;&lt;a class="toclink" href="#future-work"&gt;Future&amp;nbsp;work&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Obviously this is still a bit&amp;nbsp;rough.&lt;/p&gt;
&lt;p&gt;That thing where the player can walk on top of the textbox is a bit of a problem, since the same thing happens if the textbox opens while the player is near the bottom of the screen.  There are a couple solutions to this, and they&amp;#8217;ll really depend on how I end up deciding to display the&amp;nbsp;box.&lt;/p&gt;
&lt;p&gt;I actually wanted the glyphs to be drawn a little lower than normal on the top line, to add half a char or so of padding around them, but I tried it and got a buffer overrun that I didn&amp;#8217;t feel like investigating.  That&amp;#8217;s an obvious thing to fix next time I touch this&amp;nbsp;code.&lt;/p&gt;
&lt;p&gt;What about word wrapping?  I&amp;#8217;ve &lt;a href="https://eev.ee/blog/2016/10/20/word-wrapping-dialogue/"&gt;written about that before&lt;/a&gt; and clearly have strong opinions about it, but I &lt;em&gt;really&lt;/em&gt; don&amp;#8217;t want to do dynamic word wrapping with a variable-width font on a &lt;em&gt;Game Boy&lt;/em&gt;.  Instead, I&amp;#8217;ll probably store dialogue in some other format and use another converter script to do the word-wrapping ahead of time.  That&amp;#8217;ll also save me from writing large amounts of dialogue in, um, assembly.  And if/when I want any fancy-pants special effects within dialogue, I can describe them with a human-readable format and then convert that to more assembly-friendly bytecode&amp;nbsp;instructions.&lt;/p&gt;
&lt;p&gt;The dialogue box still doesn&amp;#8217;t &lt;em&gt;go away&lt;/em&gt;, partly because it draws right on top of the map, and I don&amp;#8217;t have any easy way to repair the map right now.  I&amp;#8217;ll probably switch to one of those other mechanisms for showing the box later that won&amp;#8217;t require clobbering the map, and then this problem will pretty much solve&amp;nbsp;itself.&lt;/p&gt;
&lt;p&gt;What about menus?  Those will either have to go inside the dialogue box (which means the question being asked isn&amp;#8217;t visible, oof), or they&amp;#8217;ll have to go in a smaller box above it like in Pokémon.  But the latter solution means I can&amp;#8217;t use the window &lt;em&gt;or&lt;/em&gt; display trickery — both of those only work reliably for horizontal splits.  I&amp;#8217;m not quite sure how to handle this,&amp;nbsp;yet.&lt;/p&gt;
&lt;p&gt;And then, what of portraits?  Most games get away without them by having a silent protagonist, which makes it obvious who&amp;#8217;s talking.  But Anise is anything but silent, so I need a stronger indicator.  I obviously can&amp;#8217;t overlay a big transparent portrait on the background, like I do in my LÖVE games.  I &lt;em&gt;think&lt;/em&gt; I can reseve space for them in the status bar, which will go underneath the dialogue box.  I&amp;#8217;ll have to see how it works out.  Maybe I could also use a different text color for every&amp;nbsp;speaker?&lt;/p&gt;
&lt;p&gt;After all &lt;em&gt;that&lt;/em&gt;, I can start worrying about other frills like colored text and pauses and whatever.&amp;nbsp;Phew.&lt;/p&gt;
&lt;h2 id="to-be-continued"&gt;&lt;a class="toclink" href="#to-be-continued"&gt;To be&amp;nbsp;continued&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;That brings us up to &lt;a href="https://github.com/eevee/anise-cheezball-rising/commit/a173dbb506b8eb56dd2d027f98d75e558a66bb2d"&gt;commit a173dbb&lt;/a&gt;, which is &lt;em&gt;slightly&lt;/em&gt; beyond the &lt;a href="https://github.com/eevee/anise-cheezball-rising/releases/tag/v20180707pre"&gt;second release&lt;/a&gt; (which includes a one-line textbox)!  Also that was &lt;em&gt;three months ago&lt;/em&gt; oh dear.  I think I&amp;#8217;ll be putting out a new release soon, stay&amp;nbsp;tuned!&lt;/p&gt;
&lt;p&gt;Next time: &lt;a href="https://eev.ee/blog/2018/11/28/cheezball-rising-collision-detection-part-1/"&gt;&lt;em&gt;collision detection&lt;/em&gt;&lt;/a&gt;!  I am&amp;nbsp;doomed.&lt;/p&gt;</content><category term="blog"></category><category term="tech"></category><category term="gamedev"></category><category term="cheezball rising"></category></entry><entry><title>Cheezball Rising: Resounding failure</title><link href="https://eev.ee/blog/2018/09/06/cheezball-rising-resounding-failure/" rel="alternate"></link><published>2018-09-06T05:00:00-07:00</published><updated>2018-09-06T05:00:00-07:00</updated><author><name>Eevee</name></author><id>tag:eev.ee,2018-09-06:/blog/2018/09/06/cheezball-rising-resounding-failure/</id><summary type="html">&lt;p&gt;This is a series about &lt;a href="https://github.com/eevee/anise-cheezball-rising"&gt;&lt;strong&gt;Star Anise Chronicles: Cheezball Rising&lt;/strong&gt;&lt;/a&gt;, an expansive adventure game about my cat for the Game Boy Color.  Follow along as I struggle to make something with this bleeding-edge console!&lt;/p&gt;
&lt;p&gt;GitHub has &lt;a href="https://github.com/eevee/anise-cheezball-rising/releases"&gt;intermittent prebuilt ROMs&lt;/a&gt;, or you can get them a week early &lt;a href="https://www.patreon.com/eevee/posts?tag=cheezball%20rising"&gt;on Patreon&lt;/a&gt; if you pledge $4.  More details in the &lt;a href="https://github.com/eevee/anise-cheezball-rising"&gt;&lt;span class="caps"&gt;README&lt;/span&gt;&lt;/a&gt;!&lt;/p&gt;
&lt;hr/&gt;
&lt;p&gt;In this issue, I &lt;s&gt;cannot&lt;/s&gt; get a goddamn Game Boy to meow at me!!&lt;/p&gt;
&lt;p&gt;Previously: &lt;a href="https://eev.ee/blog/2018/07/15/cheezball-rising-maps-and-sprites/"&gt;maps and sprites&lt;/a&gt;.&lt;br/&gt;
Next: text!&lt;/p&gt;
</summary><content type="html">&lt;p&gt;This is a series about &lt;a href="https://github.com/eevee/anise-cheezball-rising"&gt;&lt;strong&gt;Star Anise Chronicles: Cheezball Rising&lt;/strong&gt;&lt;/a&gt;, an expansive adventure game about my cat for the Game Boy Color.  Follow along as I struggle to make something with this bleeding-edge&amp;nbsp;console!&lt;/p&gt;
&lt;p&gt;GitHub has &lt;a href="https://github.com/eevee/anise-cheezball-rising/releases"&gt;intermittent prebuilt ROMs&lt;/a&gt;, or you can get them a week early &lt;a href="https://www.patreon.com/eevee/posts?tag=cheezball%20rising"&gt;on Patreon&lt;/a&gt; if you pledge $4.  More details in the &lt;a href="https://github.com/eevee/anise-cheezball-rising"&gt;&lt;span class="caps"&gt;README&lt;/span&gt;&lt;/a&gt;!&lt;/p&gt;
&lt;hr /&gt;
&lt;p&gt;In this issue, I &lt;s&gt;cannot&lt;/s&gt; get a goddamn Game Boy to meow at&amp;nbsp;me!!&lt;/p&gt;
&lt;p&gt;Previously: &lt;a href="https://eev.ee/blog/2018/07/15/cheezball-rising-maps-and-sprites/"&gt;maps and sprites&lt;/a&gt;.&lt;br /&gt;
Next:&amp;nbsp;text!&lt;/p&gt;


&lt;h2 id="recap"&gt;&lt;a class="toclink" href="#recap"&gt;Recap&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;With the power of Aseprite, Tiled, and some Python I slopped together, the game has evolved beyond Test Art and into Regular&amp;nbsp;Art.&lt;/p&gt;
&lt;div class="prose-full-illustration"&gt;
&lt;img src="https://eev.ee/media/cheezball/04l-go-anise-go-again.gif" alt="Star Anise walking around a moon environment in-game, animated in all four directions"&gt;
&lt;/div&gt;

&lt;p&gt;I&amp;#8217;ve got so much work to do on this, so it&amp;#8217;s time to prioritize.  What is absolutely &lt;em&gt;crucial&lt;/em&gt; to this&amp;nbsp;game?&lt;/p&gt;
&lt;p&gt;The answer, of course, is to make Anise meow.  Specifically, to make him &lt;a href="http://floraverse.com/comic/oneshots/568-aooowr/"&gt;&lt;span class="caps"&gt;AOOOWR&lt;/span&gt;&lt;/a&gt;.&lt;/p&gt;
&lt;h2 id="brief-audio-primer"&gt;&lt;a class="toclink" href="#brief-audio-primer"&gt;Brief audio&amp;nbsp;primer&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;What we perceive as sound is the vibration of our eardrums, caused by vibration of the air against them.  Eardrums can only move along a single axis (in or out), so no matter what chaotic things the air is doing, what we hear at a given instant is flattened down to a single scalar number: how far the eardrum has displaced from its normal&amp;nbsp;position.&lt;/p&gt;
&lt;p&gt;(There&amp;#8217;s also a bunch of stuff about tiny hairs in the back of your ear, but, close enough.  Also it&amp;#8217;s really &lt;em&gt;two&lt;/em&gt; numbers since you have two ears, but stereo channels tend to be handled&amp;nbsp;separately.)&lt;/p&gt;
&lt;p&gt;Digital audio is nothing more than a sequence of those numbers.  Of course, we can&amp;#8217;t record the displacement at every single instant, because there are infinitely many instants; instead, we take measurements (&lt;em&gt;samples&lt;/em&gt;) at regular intervals.  The interval is called the &lt;em&gt;sample rate&lt;/em&gt;, is usually a very small fraction of a second, and is generally measured in Hertz/Hz (which just means &amp;#8220;per second&amp;#8221;).  A very common sample rate is 44100 Hz, which means a measurement was taken every 0.0000227&amp;nbsp;seconds.&lt;/p&gt;
&lt;p&gt;I say &amp;#8220;measurement&amp;#8221; but the same idea applies for &lt;em&gt;generating&lt;/em&gt; sounds, which is what the Game Boy does.  Want to make a square wave?  Just generate a block of all the same positive sample, then another block of all the same negative sample, and alternate back and forth.  That&amp;#8217;s why it&amp;#8217;s depicted as a square — that&amp;#8217;s the &lt;em&gt;graph&lt;/em&gt; of how the samples vary over&amp;nbsp;time.&lt;/p&gt;
&lt;p&gt;Okay!  I hope that was enough because it&amp;#8217;s like 80% of everything I know about audio.  Let&amp;#8217;s get to the Game&amp;nbsp;Boy.&lt;/p&gt;
&lt;h2 id="game-boy-audio"&gt;&lt;a class="toclink" href="#game-boy-audio"&gt;Game Boy&amp;nbsp;audio&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;The Game Boy contains, within its mysterious depths, a teeny tiny synthesizer.  It offers a vast array of &lt;em&gt;four&lt;/em&gt; whole channels (instruments) to choose from: a square wave, also a square wave, a wavetable, and white noise.  They can each be controlled with a handful of registers, and will continually produce whatever tone they&amp;#8217;re configured for.  By changing their parameters at regular intervals, you can create a pleasing sequence of varying tones, which you humans call&amp;nbsp;&amp;#8220;music&amp;#8221;.&lt;/p&gt;
&lt;p&gt;Making music is, I&amp;#8217;m sure, going to be an absolute nightmare.  What music authoring tools am I possibly going to dig up that exactly conform to the Game Boy hardware?  I can&amp;#8217;t even begin to imagine what this pipeline might look&amp;nbsp;like.&lt;/p&gt;
&lt;p&gt;Luckily, that&amp;#8217;s not what this post is about, because I chickened out and tried something way easier&amp;nbsp;instead.&lt;/p&gt;
&lt;p&gt;Before I set out into the wilderness myself, I &lt;em&gt;did&lt;/em&gt; want to get an emulator to create any kind of noise at all, just to give myself a starting point.  There are an awful lot of audio twiddles, so I dug up a &lt;a href="http://gbdev.gg8.se/wiki/articles/Sound_tutorial"&gt;Game Boy sound tutorial&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;I became a little skeptical when the author admitted they didn&amp;#8217;t know what a square wave was, but they did provide a brief snippet of code at the end that&amp;#8217;s claimed to produce a&amp;nbsp;sound:&lt;/p&gt;
&lt;table class="highlighttable"&gt;&lt;tr&gt;&lt;td class="linenos"&gt;&lt;div class="linenodiv"&gt;&lt;pre&gt;&lt;span class="normal"&gt;1&lt;/span&gt;
&lt;span class="normal"&gt;2&lt;/span&gt;
&lt;span class="normal"&gt;3&lt;/span&gt;
&lt;span class="normal"&gt;4&lt;/span&gt;
&lt;span class="normal"&gt;5&lt;/span&gt;
&lt;span class="normal"&gt;6&lt;/span&gt;
&lt;span class="normal"&gt;7&lt;/span&gt;
&lt;span class="normal"&gt;8&lt;/span&gt;
&lt;span class="normal"&gt;9&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/td&gt;&lt;td class="code"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="n"&gt;NR52_REG&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mh"&gt;0x80&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="n"&gt;NR51_REG&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mh"&gt;0x11&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="n"&gt;NR50_REG&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mh"&gt;0x77&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;

&lt;span class="n"&gt;NR10_REG&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mh"&gt;0x1E&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="n"&gt;NR11_REG&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mh"&gt;0x10&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="n"&gt;NR12_REG&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mh"&gt;0xF3&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="n"&gt;NR13_REG&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mh"&gt;0x00&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="n"&gt;NR14_REG&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mh"&gt;0x87&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;p&gt;That&amp;#8217;s C, written for the much-maligned &lt;span class="caps"&gt;GBDK&lt;/span&gt;, which for some reason uses regular assignment to write to a specific address?  It&amp;#8217;s easy enough to translate to&amp;nbsp;rgbasm:&lt;/p&gt;
&lt;table class="highlighttable"&gt;&lt;tr&gt;&lt;td class="linenos"&gt;&lt;div class="linenodiv"&gt;&lt;pre&gt;&lt;span class="normal"&gt; 1&lt;/span&gt;
&lt;span class="normal"&gt; 2&lt;/span&gt;
&lt;span class="normal"&gt; 3&lt;/span&gt;
&lt;span class="normal"&gt; 4&lt;/span&gt;
&lt;span class="normal"&gt; 5&lt;/span&gt;
&lt;span class="normal"&gt; 6&lt;/span&gt;
&lt;span class="normal"&gt; 7&lt;/span&gt;
&lt;span class="normal"&gt; 8&lt;/span&gt;
&lt;span class="normal"&gt; 9&lt;/span&gt;
&lt;span class="normal"&gt;10&lt;/span&gt;
&lt;span class="normal"&gt;11&lt;/span&gt;
&lt;span class="normal"&gt;12&lt;/span&gt;
&lt;span class="normal"&gt;13&lt;/span&gt;
&lt;span class="normal"&gt;14&lt;/span&gt;
&lt;span class="normal"&gt;15&lt;/span&gt;
&lt;span class="normal"&gt;16&lt;/span&gt;
&lt;span class="normal"&gt;17&lt;/span&gt;
&lt;span class="normal"&gt;18&lt;/span&gt;
&lt;span class="normal"&gt;19&lt;/span&gt;
&lt;span class="normal"&gt;20&lt;/span&gt;
&lt;span class="normal"&gt;21&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/td&gt;&lt;td class="code"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;    &lt;span class="c1"&gt;; Enable sound globally&lt;/span&gt;
    &lt;span class="ow"&gt;ld&lt;/span&gt; &lt;span class="nb"&gt;a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mh"&gt;$80&lt;/span&gt;
    &lt;span class="ow"&gt;ldh&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;rAUDENA&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="nb"&gt;a&lt;/span&gt;
    &lt;span class="c1"&gt;; Enable channel 1 in stereo&lt;/span&gt;
    &lt;span class="ow"&gt;ld&lt;/span&gt; &lt;span class="nb"&gt;a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mh"&gt;$11&lt;/span&gt;
    &lt;span class="ow"&gt;ldh&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;rAUDTERM&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="nb"&gt;a&lt;/span&gt;
    &lt;span class="c1"&gt;; Set volume&lt;/span&gt;
    &lt;span class="ow"&gt;ld&lt;/span&gt; &lt;span class="nb"&gt;a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mh"&gt;$77&lt;/span&gt;
    &lt;span class="ow"&gt;ldh&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;rAUDVOL&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="nb"&gt;a&lt;/span&gt;

    &lt;span class="c1"&gt;; Configure channel 1.  See below&lt;/span&gt;
    &lt;span class="ow"&gt;ld&lt;/span&gt; &lt;span class="nb"&gt;a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mh"&gt;$1e&lt;/span&gt;
    &lt;span class="ow"&gt;ldh&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;rAUD1SWEEP&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="nb"&gt;a&lt;/span&gt;
    &lt;span class="ow"&gt;ld&lt;/span&gt; &lt;span class="nb"&gt;a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mh"&gt;$10&lt;/span&gt;
    &lt;span class="ow"&gt;ldh&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;rAUD1LEN&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="nb"&gt;a&lt;/span&gt;
    &lt;span class="ow"&gt;ld&lt;/span&gt; &lt;span class="nb"&gt;a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mh"&gt;$f3&lt;/span&gt;
    &lt;span class="ow"&gt;ldh&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;rAUD1ENV&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="nb"&gt;a&lt;/span&gt;
    &lt;span class="ow"&gt;ld&lt;/span&gt; &lt;span class="nb"&gt;a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mh"&gt;$00&lt;/span&gt;
    &lt;span class="ow"&gt;ldh&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;rAUD1LOW&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="nb"&gt;a&lt;/span&gt;
    &lt;span class="ow"&gt;ld&lt;/span&gt; &lt;span class="nb"&gt;a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mh"&gt;$85&lt;/span&gt;
    &lt;span class="ow"&gt;ldh&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;rAUD1HIGH&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="nb"&gt;a&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;p&gt;It sounds like&amp;nbsp;this.&lt;/p&gt;
&lt;p&gt;&lt;audio controls src="https://eev.ee/media/cheezball/05a-example-square-wave.wav"&gt;&lt;/audio&gt;&lt;/p&gt;
&lt;p&gt;Some explanation may be in order.  This is a big ol&amp;#8217; mess and you could just as well read &lt;a href="http://gbdev.gg8.se/wiki/articles/Sound_Controller"&gt;the wiki&amp;#8217;s article on the sound controller&lt;/a&gt;, so feel free to skip ahead a&amp;nbsp;bit.&lt;/p&gt;
&lt;p&gt;First, the official names for all of the sound registers are terrible.  They&amp;#8217;re all named &amp;#8220;NRxy&amp;#8221; — &amp;#8220;noise register&amp;#8221; perhaps? — where &lt;em&gt;x&lt;/em&gt; is the channel number (or 5 for master settings) and &lt;em&gt;y&lt;/em&gt; is just whatever.  Thankfully, hardware.inc provides some aliases that make a &lt;em&gt;little&lt;/em&gt; more sense, and those are what I&amp;#8217;ve used&amp;nbsp;above.&lt;/p&gt;
&lt;p&gt;The very first thing I &lt;em&gt;have&lt;/em&gt; to do is set the high bit of &lt;span class="caps"&gt;AUDENA&lt;/span&gt; (&lt;span class="caps"&gt;NR52&lt;/span&gt;), which toggles sound on or off entirely.  The sound system isn&amp;#8217;t like the &lt;span class="caps"&gt;LCD&lt;/span&gt;, which I might turn off temporarily while doing a lot of graphics loading; when the high bit of &lt;span class="caps"&gt;AUDENA&lt;/span&gt; is off, &lt;em&gt;all the other sound registers&lt;/em&gt; are wiped to zero and cannot be written until sound is enabled&amp;nbsp;again.&lt;/p&gt;
&lt;p&gt;The other important master registers are &lt;span class="caps"&gt;AUDVOL&lt;/span&gt; (&lt;span class="caps"&gt;NR50&lt;/span&gt;) and &lt;span class="caps"&gt;AUDTERM&lt;/span&gt; (&lt;span class="caps"&gt;NR51&lt;/span&gt;).  Both of them are split into two identical nybbles, each controlling the left or right output channel.  &lt;span class="caps"&gt;AUDVOL&lt;/span&gt; controls the master volume, from 0 to 7.  (As I understand it, the high bit is used to enable audio output from extra synthesizer hardware on the &lt;em&gt;cartridge&lt;/em&gt;, a feature I don&amp;#8217;t believe any game ever actually used.)  &lt;span class="caps"&gt;AUDTERM&lt;/span&gt; enables channels/instruments, one bit per channel.  The above code turns on channel 1, the square wave, at max volume in&amp;nbsp;stereo.&lt;/p&gt;
&lt;p&gt;Then there&amp;#8217;s just, you know, &lt;em&gt;sound stuff&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;&lt;span class="caps"&gt;AUD1HIGH&lt;/span&gt; (&lt;span class="caps"&gt;NR14&lt;/span&gt;) and &lt;span class="caps"&gt;AUD1LOW&lt;/span&gt; (&lt;span class="caps"&gt;NR13&lt;/span&gt;) are a bit of a clusterfuck, and one shared by all except the white noise channel.  The high bit of &lt;span class="caps"&gt;AUD1HIGH&lt;/span&gt; is the &amp;#8220;init&amp;#8221; bit and triggers the sound to actually play (or restart), which is why it&amp;#8217;s set last.  The second highest bit, bit 6, controls timing: if it&amp;#8217;s set, then the channel will only play for as long as a time given by &lt;span class="caps"&gt;AUD1LEN&lt;/span&gt;; if not, the channel will play&amp;nbsp;indefinitely.&lt;/p&gt;
&lt;p&gt;Finally, the interesting part: the lower three bits of &lt;span class="caps"&gt;AUD1HIGH&lt;/span&gt; and the entirety of &lt;span class="caps"&gt;AUD1LOW&lt;/span&gt; combine to make an 11-bit frequency.  Or, rather, if those 11 bits are &lt;span class="math"&gt;\(n\)&lt;/span&gt;, then the frequency is &lt;span class="math"&gt;\(\frac{131072}{2048-n}\)&lt;/span&gt;.  (Since their value appears in the denominator, they really express…  &lt;em&gt;inverse time&lt;/em&gt;, not frequency, but that&amp;#8217;s neither here nor there.)  The code above sets that 11-bit value to $500, for a frequency of 171 Hz, which in A440 is about an F&lt;sub&gt;3&lt;/sub&gt;.&lt;/p&gt;
&lt;p&gt;&lt;span class="caps"&gt;AUD1SWEEP&lt;/span&gt; (&lt;span class="caps"&gt;NR10&lt;/span&gt;) can automatically slide the frequency over time.  It distinguishes channel 1 from channel 2, which is otherwise identical but doesn&amp;#8217;t have sweep functionality.  The lower three bits are the magnitude of each change; bit 3 is a sign bit (0 for up, 1 for down), and bits 6–4 are a time that control how often the frequency changes.  (Setting the time to zero disables the sweep.)  Given a magnitude of &lt;span class="math"&gt;\(n\)&lt;/span&gt; and time &lt;span class="math"&gt;\(t\)&lt;/span&gt;, every &lt;span class="math"&gt;\(\frac{t}{128}\)&lt;/span&gt; seconds, the frequency is multiplied by &lt;span class="math"&gt;\(1 ± \frac{1}{2^n}\)&lt;/span&gt;.&lt;/p&gt;
&lt;p&gt;Note that when I say &amp;#8220;frequency&amp;#8221; here, I&amp;#8217;m referring to the 11-bit &amp;#8220;frequency&amp;#8221; value, &lt;strong&gt;not&lt;/strong&gt; the actual frequency in Hz.  A &amp;#8220;frequency&amp;#8221; of $400 corresponds to 128 Hz, but halving it to $200 produces 85 Hz, a decrease of about a third.  Doubling it is &lt;em&gt;impossible&lt;/em&gt;, because $800 doesn&amp;#8217;t fit in 11 bits.  This setup seems, ah, interesting to make music with.  Can&amp;#8217;t&amp;nbsp;wait!&lt;/p&gt;
&lt;p&gt;The above code sets this register to &amp;#x24;1e, so &lt;span class="math"&gt;\(t = 1\)&lt;/span&gt;, &lt;span class="math"&gt;\(n = 6\)&lt;/span&gt;, and the frequency is decreasing; thus every &lt;span class="math"&gt;\(\frac{1}{128}\)&lt;/span&gt; seconds, the &amp;#8220;frequency&amp;#8221; drops by &lt;span class="math"&gt;\(\frac{1}{64}\)&lt;/span&gt;.&lt;/p&gt;
&lt;p&gt;Next is &lt;span class="caps"&gt;AUD1LEN&lt;/span&gt; (&lt;span class="caps"&gt;NR11&lt;/span&gt;), so named because its lower six bits set how long the sound will play.  Again we have inverse time: given a value &lt;span class="math"&gt;\(t\)&lt;/span&gt; in the low six bits, the sound will play for &lt;span class="math"&gt;\(\frac{64-t}{256}\)&lt;/span&gt; seconds.  Here those six bits are &amp;#x24;10 or 16, so the sound lasts for &lt;span class="math"&gt;\(\frac{48}{256} = \frac{3}{16} = 0.1875\)&lt;/span&gt; seconds.  &lt;em&gt;Except&lt;/em&gt;…  as mentioned above, this only applies if bit 6 of &lt;span class="caps"&gt;AUD1HIGH&lt;/span&gt; is set, which it isn&amp;#8217;t, so this doesn&amp;#8217;t apply at all and there&amp;#8217;s no point in setting any of these bits.&amp;nbsp;Hm.&lt;/p&gt;
&lt;p&gt;The two high bits of &lt;span class="caps"&gt;AUD1LEN&lt;/span&gt; select the duty cycle, which is how long the square wave is high versus low.  (A &amp;#8220;normal&amp;#8221; square wave thus has a duty of 50%.)  Our value of 0 selects 12.5% high; the other values are 25% for 1, 50% for 2, or 75% for 3.  I do wonder if the author of this code meant to use 50% duty and put the bit in the wrong place?  If so, &lt;span class="caps"&gt;AUD1LEN&lt;/span&gt; should be $80, not&amp;nbsp;$10.&lt;/p&gt;
&lt;p&gt;Finally, &lt;span class="caps"&gt;AUD1ENV&lt;/span&gt; selects the volume envelope, which can increase or decrease over time.  Curiously, the resolution is higher here than in &lt;span class="caps"&gt;AUDVOL&lt;/span&gt; — the entire high nybble is the value of the envelope.  This value can be changed automatically over time in increments of 1: bit 3 controls the direction (0 to decrease, 1 to increase) and the low three bits control how often the value changes, counted in &lt;span class="math"&gt;\(\frac{1}{64}\)&lt;/span&gt; seconds.  For our value of &amp;#x24;f3, the volume starts out at max and decreases every &lt;span class="math"&gt;\(\frac{3}{64}\)&lt;/span&gt; seconds, so it&amp;#8217;ll stop completely (or at least be muted?) after fifteen steps or &lt;span class="math"&gt;\(\frac{45}{64} ≈ 0.7\)&lt;/span&gt;&amp;nbsp;seconds.&lt;/p&gt;
&lt;p&gt;And hey, that&amp;#8217;s all more or less what I see if I record mGBA&amp;#8217;s output in&amp;nbsp;Audacity!&lt;/p&gt;
&lt;div class="prose-full-illustration"&gt;
&lt;img src="https://eev.ee/media/cheezball/05b-demo-sound-audacity.png" alt="Waveform of the above sound"&gt;
&lt;/div&gt;

&lt;p&gt;Boy!  What a horrible slog.  Don&amp;#8217;t worry; that&amp;#8217;s a good 75% of everything there is to know about the sound registers.  The second square wave is exactly the same except it can&amp;#8217;t do a frequency sweep.  The white noise channel is similar, except that instead of frequency, it has a few knobs for controlling how the noise is generated.  And the waveform channel is what the rest of this post is&amp;nbsp;about—&lt;/p&gt;
&lt;p&gt;&lt;span class="dquo"&gt;&amp;#8220;&lt;/span&gt;Hang on!&amp;#8221; I hear you cry.  &amp;#8220;That&amp;#8217;s a mighty funny-looking &amp;#8216;square&amp;#8217;&amp;nbsp;wave.&amp;#8221;&lt;/p&gt;
&lt;p&gt;It sure is!  The Game Boy has some mighty funny sound hardware.  Don&amp;#8217;t worry about it.  I don&amp;#8217;t have any explanation, anyway.  I know the weird slope shapes are due to a high-pass filter capacitor that constantly degrades the signal gradually towards silence, but I don&amp;#8217;t know why the waveform isn&amp;#8217;t centered at zero.  (Note that mGBA has a bug and currently generates audio inverted, which is hard to notice audibly but which means the above graph is&amp;nbsp;upside-down.)&lt;/p&gt;
&lt;h2 id="the-thing-i-actually-wanted-to-do"&gt;&lt;a class="toclink" href="#the-thing-i-actually-wanted-to-do"&gt;The thing I actually wanted to&amp;nbsp;do&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Right, back to the thing I actually wanted to&amp;nbsp;do.&lt;/p&gt;
&lt;p&gt;I have a sound.  I want to play it on a Game Boy.  I know this is possible, because Pokémon Yellow does&amp;nbsp;it.&lt;/p&gt;
&lt;p&gt;Channel 3 is a wavetable channel, which means I can define a completely arbitrary waveform (read: sound) and channel 3 will play it for me.  The correct approach seems obvious: slice the sound into small chunks and ask channel 3 to play them in&amp;nbsp;sequence.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;How hard could this possibly&amp;nbsp;be?&lt;/em&gt;&lt;/p&gt;
&lt;h3 id="channel-3"&gt;&lt;a class="toclink" href="#channel-3"&gt;Channel&amp;nbsp;3&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Channel 3 plays a waveform from &lt;em&gt;waveform &lt;span class="caps"&gt;RAM&lt;/span&gt;&lt;/em&gt;, which is a block of 16 bytes in register space, from $&lt;span class="caps"&gt;FF30&lt;/span&gt; through $&lt;span class="caps"&gt;FF3F&lt;/span&gt;.  Each nybble is one sample, so I have 32 samples whose values can range from 0 to&amp;nbsp;15.&lt;/p&gt;
&lt;p&gt;32 samples is not a &lt;em&gt;whole&lt;/em&gt; lot; remember, a common audio rate is 44100 Hz.  To keep that up, I&amp;#8217;d need to fill the buffer almost 1400 times per second.  I can use a lower sample rate, but what?  I guess I&amp;#8217;ll figure that out&amp;nbsp;later.&lt;/p&gt;
&lt;p&gt;First things first: I need to take my sound and cram it into this format, somehow.  Here&amp;#8217;s the sound I&amp;#8217;m starting&amp;nbsp;with.&lt;/p&gt;
&lt;p&gt;&lt;audio controls src="https://eev.ee/media/cheezball/05c-aowr-original.wav"&gt;&lt;/audio&gt;&lt;/p&gt;
&lt;p&gt;The original recording was a bit quiet, so I popped it open in Audacity and stretched it to max volume.  I only have 4-bit samples, remember, and trying to cram a quiet sound into a low bitrate will lose most of the&amp;nbsp;detail.&lt;/p&gt;
&lt;p&gt;(A very weird thing about sound is that samples are really just measurements of &lt;em&gt;volume&lt;/em&gt;.  Every feature of sound is nothing more than a change in&amp;nbsp;volume.)&lt;/p&gt;
&lt;p&gt;Now I need to turn this into a sequence of nybbles.  From &lt;a href="https://eev.ee/blog/2016/05/30/extracting-music-from-the-pico-8/"&gt;previous adventures&lt;/a&gt;, I know that Python has a handy &lt;a href="https://docs.python.org/3/library/wave.html"&gt;&lt;code&gt;wave&lt;/code&gt; module&lt;/a&gt; for reading sample data directly from a &lt;span class="caps"&gt;WAV&lt;/span&gt; file, and so I wrote a crappy&amp;nbsp;resampler:&lt;/p&gt;
&lt;table class="highlighttable"&gt;&lt;tr&gt;&lt;td class="linenos"&gt;&lt;div class="linenodiv"&gt;&lt;pre&gt;&lt;span class="normal"&gt; 1&lt;/span&gt;
&lt;span class="normal"&gt; 2&lt;/span&gt;
&lt;span class="normal"&gt; 3&lt;/span&gt;
&lt;span class="normal"&gt; 4&lt;/span&gt;
&lt;span class="normal"&gt; 5&lt;/span&gt;
&lt;span class="normal"&gt; 6&lt;/span&gt;
&lt;span class="normal"&gt; 7&lt;/span&gt;
&lt;span class="normal"&gt; 8&lt;/span&gt;
&lt;span class="normal"&gt; 9&lt;/span&gt;
&lt;span class="normal"&gt;10&lt;/span&gt;
&lt;span class="normal"&gt;11&lt;/span&gt;
&lt;span class="normal"&gt;12&lt;/span&gt;
&lt;span class="normal"&gt;13&lt;/span&gt;
&lt;span class="normal"&gt;14&lt;/span&gt;
&lt;span class="normal"&gt;15&lt;/span&gt;
&lt;span class="normal"&gt;16&lt;/span&gt;
&lt;span class="normal"&gt;17&lt;/span&gt;
&lt;span class="normal"&gt;18&lt;/span&gt;
&lt;span class="normal"&gt;19&lt;/span&gt;
&lt;span class="normal"&gt;20&lt;/span&gt;
&lt;span class="normal"&gt;21&lt;/span&gt;
&lt;span class="normal"&gt;22&lt;/span&gt;
&lt;span class="normal"&gt;23&lt;/span&gt;
&lt;span class="normal"&gt;24&lt;/span&gt;
&lt;span class="normal"&gt;25&lt;/span&gt;
&lt;span class="normal"&gt;26&lt;/span&gt;
&lt;span class="normal"&gt;27&lt;/span&gt;
&lt;span class="normal"&gt;28&lt;/span&gt;
&lt;span class="normal"&gt;29&lt;/span&gt;
&lt;span class="normal"&gt;30&lt;/span&gt;
&lt;span class="normal"&gt;31&lt;/span&gt;
&lt;span class="normal"&gt;32&lt;/span&gt;
&lt;span class="normal"&gt;33&lt;/span&gt;
&lt;span class="normal"&gt;34&lt;/span&gt;
&lt;span class="normal"&gt;35&lt;/span&gt;
&lt;span class="normal"&gt;36&lt;/span&gt;
&lt;span class="normal"&gt;37&lt;/span&gt;
&lt;span class="normal"&gt;38&lt;/span&gt;
&lt;span class="normal"&gt;39&lt;/span&gt;
&lt;span class="normal"&gt;40&lt;/span&gt;
&lt;span class="normal"&gt;41&lt;/span&gt;
&lt;span class="normal"&gt;42&lt;/span&gt;
&lt;span class="normal"&gt;43&lt;/span&gt;
&lt;span class="normal"&gt;44&lt;/span&gt;
&lt;span class="normal"&gt;45&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/td&gt;&lt;td class="code"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;wave&lt;/span&gt;

&lt;span class="n"&gt;TARGET_RATE&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;32768&lt;/span&gt;

&lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="n"&gt;wave&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;open&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;aowr.wav&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;w&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;nchannels&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;sample_width&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;framerate&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;nframes&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;_&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;w&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;getparams&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="n"&gt;outdata&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;bytearray&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="n"&gt;gbdata&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;bytearray&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

    &lt;span class="n"&gt;frames_per_note&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;framerate&lt;/span&gt; &lt;span class="o"&gt;//&lt;/span&gt; &lt;span class="n"&gt;TARGET_RATE&lt;/span&gt;
    &lt;span class="n"&gt;nybble&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;None&lt;/span&gt;
    &lt;span class="k"&gt;while&lt;/span&gt; &lt;span class="kc"&gt;True&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;w&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;readframes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;frames_per_note&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="k"&gt;break&lt;/span&gt;

        &lt;span class="n"&gt;n&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;
        &lt;span class="n"&gt;total&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;
        &lt;span class="c1"&gt;# Left and right channels are interleaved; this will pick up data from only channel 0&lt;/span&gt;
        &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nb"&gt;range&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="n"&gt;nchannels&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;sample_width&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
            &lt;span class="n"&gt;frame&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;int&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;from_bytes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;sample_width&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;little&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;signed&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="kc"&gt;True&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="n"&gt;n&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
            &lt;span class="n"&gt;total&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="n"&gt;frame&lt;/span&gt;

        &lt;span class="c1"&gt;# Crush the new sample to a nybble&lt;/span&gt;
        &lt;span class="n"&gt;crushed_frame&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;int&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;total&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="n"&gt;n&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;sample_width&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;8&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="c1"&gt;# Expand it back to the full sample size, to make a WAV simulating how it should sound&lt;/span&gt;
        &lt;span class="n"&gt;encoded_crushed_frame&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;crushed_frame&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;sample_width&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;8&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;to_bytes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;little&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;signed&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="kc"&gt;True&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;outdata&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;extend&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;encoded_crushed_frame&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;nchannels&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;frames_per_note&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;

        &lt;span class="c1"&gt;# Combine every two nybbles together.  The manual shows that the high nybble plays first.&lt;/span&gt;
        &lt;span class="c1"&gt;# WAV data is signed, but Game Boy nybbles are not, so add the rough midpoint of 7&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;nybble&lt;/span&gt; &lt;span class="ow"&gt;is&lt;/span&gt; &lt;span class="kc"&gt;None&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="n"&gt;nybble&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;crushed_frame&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;7&lt;/span&gt;
        &lt;span class="k"&gt;else&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="n"&gt;byte&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;nybble&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;crushed_frame&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;7&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="n"&gt;gbdata&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;byte&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="n"&gt;nybble&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;None&lt;/span&gt;

    &lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="n"&gt;wave&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;open&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;aowrcrush.wav&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;wb&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;wout&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;wout&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;setparams&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;w&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;getparams&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
        &lt;span class="n"&gt;wout&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;writeframes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;outdata&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="nb"&gt;open&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;build/aowr.dat&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;wb&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;write&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;gbdata&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;p&gt;This is incredibly bad.  It integer-divides the original rate by the target rate, so if I try to resample 44100 to 32768, I&amp;#8217;ll end up recreating the same sound&amp;nbsp;again.&lt;/p&gt;
&lt;p&gt;I don&amp;#8217;t know why I started with 32768, either.  The resulting data is too big to even fit in a section!  Kicking it down to 8192 is a bit better (5 samples to 1, so the real final rate is 8820), but if I get any smaller, too many samples cancel each other out and I end up with silence!  I have no idea what I am doing&amp;nbsp;help.&lt;/p&gt;
&lt;p&gt;The &lt;code&gt;aowrcrush.wav&lt;/code&gt; file sounds a &lt;em&gt;little&lt;/em&gt; atrocious, fair&amp;nbsp;warning.&lt;/p&gt;
&lt;p&gt;&lt;audio controls src="https://eev.ee/media/cheezball/05d-aowr-crushed.wav"&gt;&lt;/audio&gt;&lt;/p&gt;
&lt;p&gt;But it seems to be correct, if I open it alongside the&amp;nbsp;original:&lt;/p&gt;
&lt;div class="prose-full-illustration"&gt;
&lt;img src="https://eev.ee/media/cheezball/05e-audacity-crush-comparison.png" alt="Waveforms of the original sound and its bitcrushed form; the latter is very blocky"&gt;
&lt;/div&gt;

&lt;p&gt;Crushing it to four bits caused the graph to stay fixed to only 16 possible values, which is why it&amp;#8217;s less smooth.  Reducing the sample rate made each sample last longer, which is why it&amp;#8217;s made up of short horizontal chunks.  (I resampled it back to 44100 for this comparison, so &lt;em&gt;really&lt;/em&gt; it&amp;#8217;s made of short horizontal chunks because each sample appears five times; Audacity wouldn&amp;#8217;t show an actual 8192 Hz file like&amp;nbsp;this.)&lt;/p&gt;
&lt;p&gt;It doesn&amp;#8217;t sound &lt;em&gt;great&lt;/em&gt;, but maybe it&amp;#8217;ll be softened when played through a Game Boy.  Worst case, I can try cleaning it up later.  Let&amp;#8217;s get to the good part: playing&amp;nbsp;it!&lt;/p&gt;
&lt;h3 id="playing-with-channel-3"&gt;&lt;a class="toclink" href="#playing-with-channel-3"&gt;Playing with channel&amp;nbsp;3&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Here we go!  First the global setup stuff I had&amp;nbsp;before.&lt;/p&gt;
&lt;table class="highlighttable"&gt;&lt;tr&gt;&lt;td class="linenos"&gt;&lt;div class="linenodiv"&gt;&lt;pre&gt;&lt;span class="normal"&gt;1&lt;/span&gt;
&lt;span class="normal"&gt;2&lt;/span&gt;
&lt;span class="normal"&gt;3&lt;/span&gt;
&lt;span class="normal"&gt;4&lt;/span&gt;
&lt;span class="normal"&gt;5&lt;/span&gt;
&lt;span class="normal"&gt;6&lt;/span&gt;
&lt;span class="normal"&gt;7&lt;/span&gt;
&lt;span class="normal"&gt;8&lt;/span&gt;
&lt;span class="normal"&gt;9&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/td&gt;&lt;td class="code"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;    &lt;span class="c1"&gt;; Enable sound globally&lt;/span&gt;
    &lt;span class="ow"&gt;ld&lt;/span&gt; &lt;span class="nb"&gt;a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mh"&gt;$80&lt;/span&gt;
    &lt;span class="ow"&gt;ldh&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;rAUDENA&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="nb"&gt;a&lt;/span&gt;
    &lt;span class="c1"&gt;; Map instruments to channels&lt;/span&gt;
    &lt;span class="ow"&gt;ld&lt;/span&gt; &lt;span class="nb"&gt;a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mh"&gt;$44&lt;/span&gt;
    &lt;span class="ow"&gt;ldh&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;rAUDTERM&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="nb"&gt;a&lt;/span&gt;
    &lt;span class="c1"&gt;; Set volume&lt;/span&gt;
    &lt;span class="ow"&gt;ld&lt;/span&gt; &lt;span class="nb"&gt;a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mh"&gt;$77&lt;/span&gt;
    &lt;span class="ow"&gt;ldh&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;rAUDVOL&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="nb"&gt;a&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;p&gt;Then some bits specific to channel&amp;nbsp;3.&lt;/p&gt;
&lt;table class="highlighttable"&gt;&lt;tr&gt;&lt;td class="linenos"&gt;&lt;div class="linenodiv"&gt;&lt;pre&gt;&lt;span class="normal"&gt; 1&lt;/span&gt;
&lt;span class="normal"&gt; 2&lt;/span&gt;
&lt;span class="normal"&gt; 3&lt;/span&gt;
&lt;span class="normal"&gt; 4&lt;/span&gt;
&lt;span class="normal"&gt; 5&lt;/span&gt;
&lt;span class="normal"&gt; 6&lt;/span&gt;
&lt;span class="normal"&gt; 7&lt;/span&gt;
&lt;span class="normal"&gt; 8&lt;/span&gt;
&lt;span class="normal"&gt; 9&lt;/span&gt;
&lt;span class="normal"&gt;10&lt;/span&gt;
&lt;span class="normal"&gt;11&lt;/span&gt;
&lt;span class="normal"&gt;12&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/td&gt;&lt;td class="code"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;    &lt;span class="ow"&gt;ld&lt;/span&gt; &lt;span class="nb"&gt;a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mh"&gt;$80&lt;/span&gt;
    &lt;span class="ow"&gt;ldh&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;rAUD3ENA&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="nb"&gt;a&lt;/span&gt;
    &lt;span class="ow"&gt;ld&lt;/span&gt; &lt;span class="nb"&gt;a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mh"&gt;$ff&lt;/span&gt;
    &lt;span class="ow"&gt;ldh&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;rAUD3LEN&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="nb"&gt;a&lt;/span&gt;
    &lt;span class="ow"&gt;ld&lt;/span&gt; &lt;span class="nb"&gt;a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mh"&gt;$20&lt;/span&gt;
    &lt;span class="ow"&gt;ldh&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;rAUD3LEVEL&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="nb"&gt;a&lt;/span&gt;
&lt;span class="nf"&gt;SAMPLE_RATE&lt;/span&gt; &lt;span class="k"&gt;EQU&lt;/span&gt; &lt;span class="mi"&gt;8192&lt;/span&gt;
&lt;span class="nf"&gt;CH3_FREQUENCY&lt;/span&gt; &lt;span class="k"&gt;set&lt;/span&gt; &lt;span class="mi"&gt;2048&lt;/span&gt; &lt;span class="err"&gt;-&lt;/span&gt; &lt;span class="mi"&gt;65536&lt;/span&gt;&lt;span class="err"&gt;/(SAMPLE_RATE&lt;/span&gt; &lt;span class="err"&gt;/&lt;/span&gt; &lt;span class="mi"&gt;32&lt;/span&gt;&lt;span class="err"&gt;)&lt;/span&gt;
    &lt;span class="ow"&gt;ld&lt;/span&gt; &lt;span class="nb"&gt;a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;LOW&lt;/span&gt;&lt;span class="err"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;CH3_FREQUENCY&lt;/span&gt;&lt;span class="err"&gt;)&lt;/span&gt;
    &lt;span class="ow"&gt;ldh&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;rAUD3LOW&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="nb"&gt;a&lt;/span&gt;
    &lt;span class="ow"&gt;ld&lt;/span&gt; &lt;span class="nb"&gt;a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mh"&gt;$80&lt;/span&gt; &lt;span class="err"&gt;|&lt;/span&gt; &lt;span class="nv"&gt;HIGH&lt;/span&gt;&lt;span class="err"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;CH3_FREQUENCY&lt;/span&gt;&lt;span class="err"&gt;)&lt;/span&gt;
    &lt;span class="ow"&gt;ldh&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;rAUD3HIGH&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="nb"&gt;a&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;p&gt;Channel 3 has its own bit for toggling it on or off in &lt;span class="caps"&gt;AUD3ENA&lt;/span&gt; (&lt;span class="caps"&gt;NR30&lt;/span&gt;); none of the other bits are used.  The other new register is &lt;span class="caps"&gt;AUD3LEVEL&lt;/span&gt; (&lt;span class="caps"&gt;NR32&lt;/span&gt;), which is sort of a global volume control.  The only bits used are 6 and 5, which make a two-bit selector.  The options&amp;nbsp;are:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;00:&amp;nbsp;mute&lt;/li&gt;
&lt;li&gt;01: play nybbles as&amp;nbsp;given&lt;/li&gt;
&lt;li&gt;10: play nybbles shifted right&amp;nbsp;1&lt;/li&gt;
&lt;li&gt;11: play nybbles shifted right&amp;nbsp;2&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Three of those are obviously useless, so 01 it is!  That&amp;#8217;s where I get the&amp;nbsp;$20.&lt;/p&gt;
&lt;p&gt;Figuring out the frequency is a little more clumsy.  I used some rgbasm features here to do it for me, and it took a bit of fiddling to get it right.  For example, why am I using 65536 instead of 131072, the factor I said was used for the square&amp;nbsp;wave?&lt;/p&gt;
&lt;p&gt;The answer is that for the longest time I kept getting this absolutely horrible output, recorded directly from&amp;nbsp;mGBA:&lt;/p&gt;
&lt;p&gt;&lt;audio controls src="https://eev.ee/media/cheezball/05f-aowr-horrible.wav"&gt;&lt;/audio&gt;&lt;/p&gt;
&lt;p&gt;I had no idea what this was supposed to be.  Turns out it&amp;#8217;s, well, roughly what happens when you halve the Game Boy&amp;#8217;s idea of frequency.  I &lt;em&gt;finally&lt;/em&gt; found out this coefficient was different from &lt;a href="http://gbdev.gg8.se/wiki/articles/Sound_Controller#FF1D_-_NR33_-_Channel_3_Frequency.27s_lower_data_.28W.29"&gt;the gbdev wiki&lt;/a&gt;.  I&amp;#8217;m guessing the factor of 2 has something to do with there being two nybbles per&amp;nbsp;byte?&lt;/p&gt;
&lt;p&gt;Then there&amp;#8217;s the division by 32, which &lt;em&gt;neither&lt;/em&gt; the manual &lt;em&gt;nor&lt;/em&gt; the gbdev wiki mention.  The frequency isn&amp;#8217;t actually the time it takes to play one &lt;em&gt;sample&lt;/em&gt;, but the time it takes to play the entire &lt;em&gt;buffer&lt;/em&gt;.  Which does make some sense — the &amp;#8220;normal&amp;#8221; use for the channel 3 is as a custom instrument, so you&amp;#8217;d want to apply the frequency to the entire waveform to get the right notes out.  This was even more of a nightmare to figure out, since it produced…  well, mostly just garbage.  I&amp;#8217;ll leave it to your&amp;nbsp;imagination.&lt;/p&gt;
&lt;table class="highlighttable"&gt;&lt;tr&gt;&lt;td class="linenos"&gt;&lt;div class="linenodiv"&gt;&lt;pre&gt;&lt;span class="normal"&gt;1&lt;/span&gt;
&lt;span class="normal"&gt;2&lt;/span&gt;
&lt;span class="normal"&gt;3&lt;/span&gt;
&lt;span class="normal"&gt;4&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/td&gt;&lt;td class="code"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;    &lt;span class="ow"&gt;ld&lt;/span&gt; &lt;span class="nb"&gt;a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;256&lt;/span&gt; &lt;span class="p"&gt;-&lt;/span&gt; &lt;span class="nv"&gt;4096&lt;/span&gt; &lt;span class="err"&gt;/&lt;/span&gt; &lt;span class="err"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;SAMPLE_RATE&lt;/span&gt; &lt;span class="err"&gt;/&lt;/span&gt; &lt;span class="nv"&gt;32&lt;/span&gt;&lt;span class="err"&gt;)&lt;/span&gt;
    &lt;span class="ow"&gt;ldh&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;rTMA&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="nb"&gt;a&lt;/span&gt;
    &lt;span class="ow"&gt;ld&lt;/span&gt; &lt;span class="nb"&gt;a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;4&lt;/span&gt;
    &lt;span class="ow"&gt;ldh&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;rTAC&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="nb"&gt;a&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;p&gt;Oho!  &lt;span class="caps"&gt;TMA&lt;/span&gt; and &lt;span class="caps"&gt;TAC&lt;/span&gt; are&amp;nbsp;new.&lt;/p&gt;
&lt;p&gt;The &lt;span class="caps"&gt;CPU&lt;/span&gt; has a timer register, &lt;span class="caps"&gt;TIMA&lt;/span&gt;, which counts up every…  well, every so often.  It&amp;#8217;s only a single byte, and when it overflows, it generates a &lt;em&gt;timer interrupt&lt;/em&gt;.  It then resets to the value of &lt;span class="caps"&gt;TMA&lt;/span&gt;.&lt;/p&gt;
&lt;p&gt;&lt;span class="caps"&gt;TAC&lt;/span&gt; is the timer controller.  Bit 2 enables the timer, and the lower two bits select how fast the clock counts&amp;nbsp;up.&lt;/p&gt;
&lt;p&gt;Above, I&amp;#8217;m using clock speed 00, which is 4096 Hz.  The expression for &lt;span class="caps"&gt;TMA&lt;/span&gt; computes &lt;code&gt;SAMPLE_RATE / 32&lt;/code&gt;, which is the number of times per second that the entire waveform should play, and then divides that into 4096 to get the number of timer ticks that the waveform plays for.  Subtract that from 256, and I have the value &lt;span class="caps"&gt;TIMA&lt;/span&gt; should start with to ensure that it overflows at the right&amp;nbsp;intervals.&lt;/p&gt;
&lt;p&gt;I note that this will cause a timer interrupt &lt;em&gt;256 times per second&lt;/em&gt;, which sounds like a lot on a &lt;span class="caps"&gt;CPU&lt;/span&gt;-constrained system.  It&amp;#8217;s only 4 or 5 interrupts per &lt;em&gt;frame&lt;/em&gt;, though, so maybe it won&amp;#8217;t intrude too much.  I&amp;#8217;ll burn down that bridge when I come to&amp;nbsp;it.&lt;/p&gt;
&lt;p&gt;Now I just need to enable timer&amp;nbsp;interrupts:&lt;/p&gt;
&lt;table class="highlighttable"&gt;&lt;tr&gt;&lt;td class="linenos"&gt;&lt;div class="linenodiv"&gt;&lt;pre&gt;&lt;span class="normal"&gt;1&lt;/span&gt;
&lt;span class="normal"&gt;2&lt;/span&gt;
&lt;span class="normal"&gt;3&lt;/span&gt;
&lt;span class="normal"&gt;4&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/td&gt;&lt;td class="code"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="nf"&gt;start:&lt;/span&gt;
    &lt;span class="c1"&gt;; Enable interrupts&lt;/span&gt;
    &lt;span class="ow"&gt;ld&lt;/span&gt; &lt;span class="nb"&gt;a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;IEF_TIMER&lt;/span&gt; &lt;span class="err"&gt;|&lt;/span&gt; &lt;span class="nv"&gt;IEF_VBLANK&lt;/span&gt;
    &lt;span class="ow"&gt;ldh&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;rIE&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="nb"&gt;a&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;p&gt;And of course do a call in the timer interrupt, which you may remember is a fixed place in the&amp;nbsp;header:&lt;/p&gt;
&lt;table class="highlighttable"&gt;&lt;tr&gt;&lt;td class="linenos"&gt;&lt;div class="linenodiv"&gt;&lt;pre&gt;&lt;span class="normal"&gt;1&lt;/span&gt;
&lt;span class="normal"&gt;2&lt;/span&gt;
&lt;span class="normal"&gt;3&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/td&gt;&lt;td class="code"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="k"&gt;SECTION &amp;quot;Timer overflow interrupt&amp;quot;, ROM0[$0050]&lt;/span&gt;
    &lt;span class="ow"&gt;call&lt;/span&gt; &lt;span class="nv"&gt;update_aowr&lt;/span&gt;
    &lt;span class="ow"&gt;reti&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;p&gt;One last gotcha: I discovered that timer interrupts can fire &lt;em&gt;during &lt;span class="caps"&gt;OAM&lt;/span&gt; &lt;span class="caps"&gt;DMA&lt;/span&gt;&lt;/em&gt;, a time when most of the memory map is inaccessible.  That&amp;#8217;s pretty bad!  So I also added &lt;code&gt;di&lt;/code&gt; and &lt;code&gt;ei&lt;/code&gt; around my &lt;span class="caps"&gt;DMA&lt;/span&gt;&amp;nbsp;call.&lt;/p&gt;
&lt;p&gt;Okay!  I&amp;#8217;m so close!  All that&amp;#8217;s left is the implementation of &lt;code&gt;update_aowr&lt;/code&gt;.&lt;/p&gt;
&lt;h3 id="updating-the-waveform"&gt;&lt;a class="toclink" href="#updating-the-waveform"&gt;Updating the&amp;nbsp;waveform&lt;/a&gt;&lt;/h3&gt;
&lt;table class="highlighttable"&gt;&lt;tr&gt;&lt;td class="linenos"&gt;&lt;div class="linenodiv"&gt;&lt;pre&gt;&lt;span class="normal"&gt; 1&lt;/span&gt;
&lt;span class="normal"&gt; 2&lt;/span&gt;
&lt;span class="normal"&gt; 3&lt;/span&gt;
&lt;span class="normal"&gt; 4&lt;/span&gt;
&lt;span class="normal"&gt; 5&lt;/span&gt;
&lt;span class="normal"&gt; 6&lt;/span&gt;
&lt;span class="normal"&gt; 7&lt;/span&gt;
&lt;span class="normal"&gt; 8&lt;/span&gt;
&lt;span class="normal"&gt; 9&lt;/span&gt;
&lt;span class="normal"&gt;10&lt;/span&gt;
&lt;span class="normal"&gt;11&lt;/span&gt;
&lt;span class="normal"&gt;12&lt;/span&gt;
&lt;span class="normal"&gt;13&lt;/span&gt;
&lt;span class="normal"&gt;14&lt;/span&gt;
&lt;span class="normal"&gt;15&lt;/span&gt;
&lt;span class="normal"&gt;16&lt;/span&gt;
&lt;span class="normal"&gt;17&lt;/span&gt;
&lt;span class="normal"&gt;18&lt;/span&gt;
&lt;span class="normal"&gt;19&lt;/span&gt;
&lt;span class="normal"&gt;20&lt;/span&gt;
&lt;span class="normal"&gt;21&lt;/span&gt;
&lt;span class="normal"&gt;22&lt;/span&gt;
&lt;span class="normal"&gt;23&lt;/span&gt;
&lt;span class="normal"&gt;24&lt;/span&gt;
&lt;span class="normal"&gt;25&lt;/span&gt;
&lt;span class="normal"&gt;26&lt;/span&gt;
&lt;span class="normal"&gt;27&lt;/span&gt;
&lt;span class="normal"&gt;28&lt;/span&gt;
&lt;span class="normal"&gt;29&lt;/span&gt;
&lt;span class="normal"&gt;30&lt;/span&gt;
&lt;span class="normal"&gt;31&lt;/span&gt;
&lt;span class="normal"&gt;32&lt;/span&gt;
&lt;span class="normal"&gt;33&lt;/span&gt;
&lt;span class="normal"&gt;34&lt;/span&gt;
&lt;span class="normal"&gt;35&lt;/span&gt;
&lt;span class="normal"&gt;36&lt;/span&gt;
&lt;span class="normal"&gt;37&lt;/span&gt;
&lt;span class="normal"&gt;38&lt;/span&gt;
&lt;span class="normal"&gt;39&lt;/span&gt;
&lt;span class="normal"&gt;40&lt;/span&gt;
&lt;span class="normal"&gt;41&lt;/span&gt;
&lt;span class="normal"&gt;42&lt;/span&gt;
&lt;span class="normal"&gt;43&lt;/span&gt;
&lt;span class="normal"&gt;44&lt;/span&gt;
&lt;span class="normal"&gt;45&lt;/span&gt;
&lt;span class="normal"&gt;46&lt;/span&gt;
&lt;span class="normal"&gt;47&lt;/span&gt;
&lt;span class="normal"&gt;48&lt;/span&gt;
&lt;span class="normal"&gt;49&lt;/span&gt;
&lt;span class="normal"&gt;50&lt;/span&gt;
&lt;span class="normal"&gt;51&lt;/span&gt;
&lt;span class="normal"&gt;52&lt;/span&gt;
&lt;span class="normal"&gt;53&lt;/span&gt;
&lt;span class="normal"&gt;54&lt;/span&gt;
&lt;span class="normal"&gt;55&lt;/span&gt;
&lt;span class="normal"&gt;56&lt;/span&gt;
&lt;span class="normal"&gt;57&lt;/span&gt;
&lt;span class="normal"&gt;58&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/td&gt;&lt;td class="code"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="nf"&gt;aowr:&lt;/span&gt;
&lt;span class="k"&gt;INCBIN &amp;quot;build/aowr.dat&amp;quot;&lt;/span&gt;
&lt;span class="nf"&gt;aowr_end:&lt;/span&gt;

&lt;span class="c1"&gt;; ...&lt;/span&gt;

&lt;span class="nf"&gt;update_aowr:&lt;/span&gt;
    &lt;span class="ow"&gt;push&lt;/span&gt; &lt;span class="nb"&gt;hl&lt;/span&gt;
    &lt;span class="ow"&gt;push&lt;/span&gt; &lt;span class="nb"&gt;bc&lt;/span&gt;
    &lt;span class="ow"&gt;push&lt;/span&gt; &lt;span class="nb"&gt;de&lt;/span&gt;
    &lt;span class="ow"&gt;push&lt;/span&gt; &lt;span class="nb"&gt;af&lt;/span&gt;

    &lt;span class="c1"&gt;; The current play position is stored in music_offset, a&lt;/span&gt;
    &lt;span class="c1"&gt;; word in RAM somewhere.  Load its value into de&lt;/span&gt;
    &lt;span class="ow"&gt;ld&lt;/span&gt; &lt;span class="nb"&gt;hl&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;music_offset&lt;/span&gt;
    &lt;span class="ow"&gt;ld&lt;/span&gt; &lt;span class="nb"&gt;d&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;hl&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="ow"&gt;inc&lt;/span&gt; &lt;span class="nb"&gt;hl&lt;/span&gt;
    &lt;span class="ow"&gt;ld&lt;/span&gt; &lt;span class="nb"&gt;e&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;hl&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;

    &lt;span class="c1"&gt;; Compare this to aowr_end.  If it&amp;#39;s &amp;gt;=, we&amp;#39;ve reached the&lt;/span&gt;
    &lt;span class="c1"&gt;; end of the sound, so stop here.  (Note that the timer&lt;/span&gt;
    &lt;span class="c1"&gt;; interrupt will keep firing!  This code is a first pass.)&lt;/span&gt;
    &lt;span class="ow"&gt;ld&lt;/span&gt; &lt;span class="nb"&gt;hl&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;aowr_end&lt;/span&gt;
    &lt;span class="ow"&gt;ld&lt;/span&gt; &lt;span class="nb"&gt;a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;d&lt;/span&gt;
    &lt;span class="ow"&gt;cp&lt;/span&gt; &lt;span class="nb"&gt;a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;h&lt;/span&gt;
    &lt;span class="ow"&gt;jr&lt;/span&gt; &lt;span class="nv"&gt;nc&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="err"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;done&lt;/span&gt;
    &lt;span class="ow"&gt;jr&lt;/span&gt; &lt;span class="nv"&gt;nz&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="err"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;continue&lt;/span&gt;
    &lt;span class="ow"&gt;ld&lt;/span&gt; &lt;span class="nb"&gt;a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;e&lt;/span&gt;
    &lt;span class="ow"&gt;cp&lt;/span&gt; &lt;span class="nb"&gt;a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;l&lt;/span&gt;
    &lt;span class="ow"&gt;jr&lt;/span&gt; &lt;span class="nv"&gt;nc&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="err"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;done&lt;/span&gt;
    &lt;span class="ow"&gt;jr&lt;/span&gt; &lt;span class="nv"&gt;z&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="err"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;done&lt;/span&gt;
&lt;span class="nf"&gt;.continue:&lt;/span&gt;

    &lt;span class="c1"&gt;; Copy the play position back into hl, and copy 16 bytes&lt;/span&gt;
    &lt;span class="c1"&gt;; into waveform RAM.  This unrolled loop is as quick as&lt;/span&gt;
    &lt;span class="c1"&gt;; possible, to keep the gap between chunks short.&lt;/span&gt;
    &lt;span class="ow"&gt;ld&lt;/span&gt; &lt;span class="nb"&gt;h&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;d&lt;/span&gt;
    &lt;span class="ow"&gt;ld&lt;/span&gt; &lt;span class="nb"&gt;l&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;e&lt;/span&gt;
&lt;span class="nf"&gt;_addr&lt;/span&gt; &lt;span class="k"&gt;=&lt;/span&gt; &lt;span class="err"&gt;_AUD&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="err"&gt;WAVERAM&lt;/span&gt;
    &lt;span class="k"&gt;REPT 16&lt;/span&gt;
    &lt;span class="ow"&gt;ld&lt;/span&gt; &lt;span class="nb"&gt;a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;hl&lt;/span&gt;&lt;span class="p"&gt;+]&lt;/span&gt;
    &lt;span class="ow"&gt;ldh&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;_addr&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="nb"&gt;a&lt;/span&gt;
&lt;span class="nf"&gt;_addr&lt;/span&gt; &lt;span class="k"&gt;=&lt;/span&gt; &lt;span class="err"&gt;_addr&lt;/span&gt; &lt;span class="err"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
    &lt;span class="k"&gt;ENDR&lt;/span&gt;

    &lt;span class="c1"&gt;; Write the new play position into music_offset&lt;/span&gt;
    &lt;span class="ow"&gt;ld&lt;/span&gt; &lt;span class="nb"&gt;d&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;h&lt;/span&gt;
    &lt;span class="ow"&gt;ld&lt;/span&gt; &lt;span class="nb"&gt;e&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;l&lt;/span&gt;
    &lt;span class="ow"&gt;ld&lt;/span&gt; &lt;span class="nb"&gt;hl&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;music_offset&lt;/span&gt;
    &lt;span class="ow"&gt;ld&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;hl&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="nb"&gt;d&lt;/span&gt;
    &lt;span class="ow"&gt;inc&lt;/span&gt; &lt;span class="nb"&gt;hl&lt;/span&gt;
    &lt;span class="ow"&gt;ld&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;hl&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="nb"&gt;e&lt;/span&gt;
&lt;span class="nf"&gt;.done:&lt;/span&gt;
    &lt;span class="ow"&gt;pop&lt;/span&gt; &lt;span class="nb"&gt;af&lt;/span&gt;
    &lt;span class="ow"&gt;pop&lt;/span&gt; &lt;span class="nb"&gt;de&lt;/span&gt;
    &lt;span class="ow"&gt;pop&lt;/span&gt; &lt;span class="nb"&gt;bc&lt;/span&gt;
    &lt;span class="ow"&gt;pop&lt;/span&gt; &lt;span class="nb"&gt;hl&lt;/span&gt;
    &lt;span class="ow"&gt;ret&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;p&gt;Perfect!  Let&amp;#8217;s give it a&amp;nbsp;try.&lt;/p&gt;
&lt;p&gt;&lt;audio controls src="https://eev.ee/media/cheezball/05g-aowr-mgba.wav"&gt;&lt;/audio&gt;&lt;/p&gt;
&lt;p&gt;Hey, that&amp;#8217;s not too bad!  I can see wiring that up to a button and pressing it relentlessly.  It&amp;#8217;s a bit rough, but it&amp;#8217;s not bad for this first&amp;nbsp;attempt.&lt;/p&gt;
&lt;p&gt;…&lt;/p&gt;
&lt;p&gt;That &lt;em&gt;was&lt;/em&gt; mGBA, though, and I&amp;#8217;ve had surprising problems before because I was reading or writing when the actual hardware wouldn&amp;#8217;t let me.  I guess it wouldn&amp;#8217;t hurt to try in bgb.  (&lt;em&gt;warning: very bad&lt;/em&gt;)&lt;/p&gt;
&lt;p&gt;&lt;audio controls src="https://eev.ee/media/cheezball/05h-disaster.wav"&gt;&lt;/audio&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;&lt;span class="caps"&gt;OH&lt;/span&gt; &lt;span class="caps"&gt;NO&lt;/span&gt;&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;What has&amp;nbsp;happened.&lt;/p&gt;
&lt;h2 id="tragedy"&gt;&lt;a class="toclink" href="#tragedy"&gt;Tragedy&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;A lot of fussing around, reading about &lt;a href="http://gbdev.gg8.se/wiki/articles/Gameboy_sound_hardware#Obscure_Behavior"&gt;obscure trivia&lt;/a&gt;, and being directed to &lt;a href="https://github.com/DevEd2/SamplePlayer"&gt;SamplePlayer&lt;/a&gt; taught me a valuable lesson: you cannot write to waveform &lt;span class="caps"&gt;RAM&lt;/span&gt; while the wave channel is&amp;nbsp;playing.&lt;/p&gt;
&lt;p&gt;Okay.  No problem.  I&amp;#8217;ll just turn it off, write to wave &lt;span class="caps"&gt;RAM&lt;/span&gt;, then turn it back on.  Turning it off clears the frequency, but that&amp;#8217;s fine, I can just write it&amp;nbsp;again.&lt;/p&gt;
&lt;table class="highlighttable"&gt;&lt;tr&gt;&lt;td class="linenos"&gt;&lt;div class="linenodiv"&gt;&lt;pre&gt;&lt;span class="normal"&gt; 1&lt;/span&gt;
&lt;span class="normal"&gt; 2&lt;/span&gt;
&lt;span class="normal"&gt; 3&lt;/span&gt;
&lt;span class="normal"&gt; 4&lt;/span&gt;
&lt;span class="normal"&gt; 5&lt;/span&gt;
&lt;span class="normal"&gt; 6&lt;/span&gt;
&lt;span class="normal"&gt; 7&lt;/span&gt;
&lt;span class="normal"&gt; 8&lt;/span&gt;
&lt;span class="normal"&gt; 9&lt;/span&gt;
&lt;span class="normal"&gt;10&lt;/span&gt;
&lt;span class="normal"&gt;11&lt;/span&gt;
&lt;span class="normal"&gt;12&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/td&gt;&lt;td class="code"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;    &lt;span class="c1"&gt;; Disable channel 3 to allow writing to wave RAM&lt;/span&gt;
    &lt;span class="ow"&gt;xor&lt;/span&gt; &lt;span class="nb"&gt;a&lt;/span&gt;
    &lt;span class="ow"&gt;ldh&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;rAUD3ENA&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="nb"&gt;a&lt;/span&gt;

    &lt;span class="c1"&gt;; ... do the copy ...&lt;/span&gt;

    &lt;span class="ow"&gt;ld&lt;/span&gt; &lt;span class="nb"&gt;a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mh"&gt;$80&lt;/span&gt;
    &lt;span class="ow"&gt;ldh&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;rAUD3ENA&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="nb"&gt;a&lt;/span&gt;
    &lt;span class="ow"&gt;ld&lt;/span&gt; &lt;span class="nb"&gt;a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;LOW&lt;/span&gt;&lt;span class="err"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;CH3_FREQUENCY&lt;/span&gt;&lt;span class="err"&gt;)&lt;/span&gt;
    &lt;span class="ow"&gt;ldh&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;rAUD3LOW&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="nb"&gt;a&lt;/span&gt;
    &lt;span class="ow"&gt;ld&lt;/span&gt; &lt;span class="nb"&gt;a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mh"&gt;$80&lt;/span&gt; &lt;span class="err"&gt;|&lt;/span&gt; &lt;span class="nv"&gt;HIGH&lt;/span&gt;&lt;span class="err"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;CH3_FREQUENCY&lt;/span&gt;&lt;span class="err"&gt;)&lt;/span&gt;
    &lt;span class="ow"&gt;ldh&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;rAUD3HIGH&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="nb"&gt;a&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;p&gt;Okay!  Perfect!  I&amp;#8217;m so ready for a&amp;nbsp;meow!!!&lt;/p&gt;
&lt;p&gt;&lt;audio controls src="https://eev.ee/media/cheezball/05i-spiky.wav"&gt;&lt;/audio&gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;why god&amp;nbsp;why&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;This is what I get in mGBA and SameBoy.  Ironically, it plays fine in&amp;nbsp;bgb.&lt;/p&gt;
&lt;p&gt;It seems I have come to an&amp;nbsp;impasse.&lt;/p&gt;
&lt;h3 id="why"&gt;&lt;a class="toclink" href="#why"&gt;Why&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;After a Herculean amount of debugging and discussion with people who actually know what they&amp;#8217;re talking about, here&amp;#8217;s what I understand to be&amp;nbsp;happening.&lt;/p&gt;
&lt;p&gt;When the wave channel first starts playing, it doesn&amp;#8217;t correctly read the very first nybble; instead, it uses the high nybble of whatever was already in its own internal&amp;nbsp;buffer.&lt;/p&gt;
&lt;p&gt;Disabling the wave channel sets its internal buffer to all&amp;nbsp;zeroes.&lt;/p&gt;
&lt;p&gt;I disable the wave channel every time it plays.  Effectively, every 32nd sample starting with the first is treated as zero, which is the most extreme negative value, which is why the playback looks like this (bearing in mind that mGBA&amp;#8217;s audio is currently&amp;nbsp;upside-down):&lt;/p&gt;
&lt;div class="prose-full-illustration"&gt;
&lt;img src="https://eev.ee/media/cheezball/05j-spike-visualized.png" alt="The above sound's waveform, which resembles the original, but with regularly spaced spikes"&gt;
&lt;/div&gt;

&lt;p&gt;For whatever reason, bgb doesn&amp;#8217;t emulate this spiking, so it plays fine.  I&amp;#8217;m told the spiking also happens on actual hardware, but the speakers are cheap so it&amp;#8217;s harder to&amp;nbsp;notice.&lt;/p&gt;
&lt;p&gt;SamplePlayer isn&amp;#8217;t much help here, because it&amp;#8217;s subject to the same&amp;nbsp;problem.&lt;/p&gt;
&lt;h3 id="a-ray-of-hope-dashed"&gt;&lt;a class="toclink" href="#a-ray-of-hope-dashed"&gt;A ray of hope,&amp;nbsp;dashed&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;But wait!  There&amp;#8217;s one last thing I can try.  Pokémon Yellow has freeform sounds in it, and it doesn&amp;#8217;t have this spiking!  There&amp;#8217;s even a fan disassembly of&amp;nbsp;it!&lt;/p&gt;
&lt;p&gt;Alas.  Pokémon Yellow doesn&amp;#8217;t use channel 3 to play back sounds.  It uses channel &lt;em&gt;1&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;How, you ask?  Remember when I said earlier that hearing is really just detecting changes in volume?  Pokémon Yellow plays a constant square wave and simply &lt;em&gt;toggles it on and off&lt;/em&gt;, very rapidly.  Channel 3 is 4-bit; the sounds Pokémon Yellow plays are &lt;em&gt;1-bit&lt;/em&gt;, on or off.  It&amp;#8217;s baffling, but it does&amp;nbsp;work.&lt;/p&gt;
&lt;p&gt;I don&amp;#8217;t think it&amp;#8217;ll work for &lt;em&gt;me&lt;/em&gt;, since that means 32 times as many interrupts.  In fact, Pokémon Yellow uses a busy loop as a timer, so it effectively freezes the entire rest of the game anytime it plays a Pikachu sound.  I&amp;#8217;d rather not do that, but…  I don&amp;#8217;t seem to have a lot of&amp;nbsp;options.&lt;/p&gt;
&lt;p&gt;And so I&amp;#8217;ve reached a dead end.  The spiking seems to be a fundamental bug with the Game Boy sound hardware.  I&amp;#8217;ve found evidence that it may even still exist in the &lt;span class="caps"&gt;GBA&lt;/span&gt;, which uses a superset of the same hardware.  I can&amp;#8217;t fix it, I don&amp;#8217;t see how to work around it, and it sounds &lt;em&gt;really incredibly bad&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;After days of effort trying to get this to work, I had to shelve&amp;nbsp;it.&lt;/p&gt;
&lt;p&gt;The title of this post is a sort of &lt;em&gt;pun&lt;/em&gt;, you see, a play on&amp;nbsp;words—&lt;/p&gt;
&lt;h2 id="update-dramatic-turnaround"&gt;&lt;a class="toclink" href="#update-dramatic-turnaround"&gt;UPDATE: DRAMATIC&amp;nbsp;TURNAROUND&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;&lt;span class="caps"&gt;HOT&lt;/span&gt; &lt;span class="caps"&gt;DAMN&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;With the problem laid bare, the homebrew community came through with a brilliant&amp;nbsp;suggestion.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Re-enable channel 3, &lt;em&gt;but mute it&lt;/em&gt;.&lt;/li&gt;
&lt;li&gt;Start playing at a very high frequency, to get through the first sample as quickly as&amp;nbsp;possible.&lt;/li&gt;
&lt;li&gt;Change the frequency back to normal and &lt;em&gt;restart the channel&lt;/em&gt;, via the high bit in &lt;code&gt;AUD3HIGH&lt;/code&gt;.  Since the first byte is now in the buffer, the first sample should play&amp;nbsp;correctly!&lt;/li&gt;
&lt;li&gt;Don&amp;#8217;t forget to unmute the&amp;nbsp;channel!&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;There are some tricky bits&amp;nbsp;here.&lt;/p&gt;
&lt;p&gt;The first is how to mute the channel.  The manual &lt;em&gt;claims&lt;/em&gt; that using zero in &lt;code&gt;AUD3LEVEL&lt;/code&gt; will mute the sound, but both mGBA and SameBoy seem to agree that it actually plays &lt;em&gt;all zeroes&lt;/em&gt; — maximum negative amplitude.  That&amp;#8217;ll just recreate the spikes again, so that&amp;#8217;s out.  There&amp;#8217;s also the master volume knob &lt;code&gt;AUDVOL&lt;/code&gt;, but that won&amp;#8217;t work if I ever try to play a sound at the same time as I&amp;#8217;m playing music or something.  That basically just leaves &lt;code&gt;AUDTERM&lt;/code&gt;, which lets me unplug the wave channel entirely — and luckily, the hardware is still generating output even if it&amp;#8217;s not going&amp;nbsp;anywhere.&lt;/p&gt;
&lt;p&gt;The second is &lt;em&gt;what&lt;/em&gt; frequency to use, exactly.  The obvious thing to try is the maximum possible frequency, which turns out to be 2 MHz…  but the Game Boy &lt;span class="caps"&gt;CPU&lt;/span&gt; only runs at 1 MHz, and it takes a couple instructions to change the frequency a second time, so a whole bunch of samples will have played in the&amp;nbsp;meantime.&lt;/p&gt;
&lt;p&gt;Instead, I worked backwards.  If I want a sample to last &lt;span class="math"&gt;\(n\)&lt;/span&gt; cycles, then the entire waveform lasts &lt;span class="math"&gt;\(32 n\)&lt;/span&gt; cycles, for a frequency of &lt;span class="math"&gt;\(\frac{2^{20}}{32 n} = \frac{2^{15}}{n}\)&lt;/span&gt; Hz (obtained by dividing into the &lt;span class="caps"&gt;CPU&lt;/span&gt; speed).  The frequency value I need is thus &lt;span class="math"&gt;\(2048 - \frac{65536}{\frac{2^{15}}{n}} = 2048 - 2 n\)&lt;/span&gt;.  Counting the actual work I need to do: &lt;code&gt;ld a, CONSTANT&lt;/code&gt; takes 2 cycles, and &lt;code&gt;ldh [CONSTANT], a&lt;/code&gt; takes 3, so changing the frequency takes 10 cycles.  I need to be &lt;em&gt;between&lt;/em&gt; the first and second samples at this point, so let&amp;#8217;s say one sample takes 8 cycles to play, and the value I want is 2032.&amp;nbsp;Cool.&lt;/p&gt;
&lt;p&gt;I tried this all out, and…  it totally didn&amp;#8217;t work!  &lt;em&gt;In mGBA.&lt;/em&gt;  But it &lt;em&gt;did&lt;/em&gt; work in SameBoy (which I am told has very very good audio emulation), so this still gave me a(nother) ray of&amp;nbsp;hope.&lt;/p&gt;
&lt;p&gt;I sprinkled mGBA with debug code and finally discovered something suspicious.  Its &amp;#8220;mixer&amp;#8221; looks like this&amp;nbsp;(simplified):&lt;/p&gt;
&lt;table class="highlighttable"&gt;&lt;tr&gt;&lt;td class="linenos"&gt;&lt;div class="linenodiv"&gt;&lt;pre&gt;&lt;span class="normal"&gt; 1&lt;/span&gt;
&lt;span class="normal"&gt; 2&lt;/span&gt;
&lt;span class="normal"&gt; 3&lt;/span&gt;
&lt;span class="normal"&gt; 4&lt;/span&gt;
&lt;span class="normal"&gt; 5&lt;/span&gt;
&lt;span class="normal"&gt; 6&lt;/span&gt;
&lt;span class="normal"&gt; 7&lt;/span&gt;
&lt;span class="normal"&gt; 8&lt;/span&gt;
&lt;span class="normal"&gt; 9&lt;/span&gt;
&lt;span class="normal"&gt;10&lt;/span&gt;
&lt;span class="normal"&gt;11&lt;/span&gt;
&lt;span class="normal"&gt;12&lt;/span&gt;
&lt;span class="normal"&gt;13&lt;/span&gt;
&lt;span class="normal"&gt;14&lt;/span&gt;
&lt;span class="normal"&gt;15&lt;/span&gt;
&lt;span class="normal"&gt;16&lt;/span&gt;
&lt;span class="normal"&gt;17&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/td&gt;&lt;td class="code"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;dcOffset&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;8&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;sample&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;dcOffset&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;audio&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;playingCh1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="n"&gt;sample&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;-=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;audio&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;ch1&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;sample&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;audio&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;playingCh2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="n"&gt;sample&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;-=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;audio&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;ch2&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;sample&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;audio&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;playingCh3&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="n"&gt;sample&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;-=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;audio&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;ch3&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;sample&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;audio&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;playingCh4&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="n"&gt;sample&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;-=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;audio&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;ch4&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;sample&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;sample&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;p&gt;As we&amp;#8217;ve seen, channel 3 uses unsigned values for its samples, so to convert them to virtually any modern audio format, you do need to subtract a midpoint to get a signed value.  That&amp;#8217;s what a &lt;span class="caps"&gt;DC&lt;/span&gt; offset&amp;nbsp;is.&lt;/p&gt;
&lt;p&gt;But this code isn&amp;#8217;t &lt;em&gt;quite&lt;/em&gt; doing that.  It applies the &lt;span class="caps"&gt;DC&lt;/span&gt; offset &lt;em&gt;once&lt;/em&gt; for the entire mixer, even though all four channels are&amp;nbsp;unsigned.&lt;/p&gt;
&lt;p&gt;This explains why I was still spiking in mGBA, though!  Once I&amp;#8217;d unplugged channel 3, the audio system was still enabled &lt;em&gt;but zero channels were playing&lt;/em&gt;, so the above code would produce &lt;code&gt;8&lt;/code&gt;, which is interpreted as a signed value, which is… maximum amplitude!  Aha!&amp;nbsp;Spikes!&lt;/p&gt;
&lt;p&gt;I changed it to&amp;nbsp;this:&lt;/p&gt;
&lt;table class="highlighttable"&gt;&lt;tr&gt;&lt;td class="linenos"&gt;&lt;div class="linenodiv"&gt;&lt;pre&gt;&lt;span class="normal"&gt; 1&lt;/span&gt;
&lt;span class="normal"&gt; 2&lt;/span&gt;
&lt;span class="normal"&gt; 3&lt;/span&gt;
&lt;span class="normal"&gt; 4&lt;/span&gt;
&lt;span class="normal"&gt; 5&lt;/span&gt;
&lt;span class="normal"&gt; 6&lt;/span&gt;
&lt;span class="normal"&gt; 7&lt;/span&gt;
&lt;span class="normal"&gt; 8&lt;/span&gt;
&lt;span class="normal"&gt; 9&lt;/span&gt;
&lt;span class="normal"&gt;10&lt;/span&gt;
&lt;span class="normal"&gt;11&lt;/span&gt;
&lt;span class="normal"&gt;12&lt;/span&gt;
&lt;span class="normal"&gt;13&lt;/span&gt;
&lt;span class="normal"&gt;14&lt;/span&gt;
&lt;span class="normal"&gt;15&lt;/span&gt;
&lt;span class="normal"&gt;16&lt;/span&gt;
&lt;span class="normal"&gt;17&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/td&gt;&lt;td class="code"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;dcOffset&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;8&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;sample&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;audio&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;playingCh1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="n"&gt;sample&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;+=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;audio&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;ch1&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;sample&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;dcOffset&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;audio&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;playingCh2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="n"&gt;sample&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;+=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;audio&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;ch2&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;sample&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;dcOffset&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;audio&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;playingCh3&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="n"&gt;sample&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;+=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;audio&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;ch3&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;sample&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;dcOffset&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;audio&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;playingCh4&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="n"&gt;sample&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;+=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;audio&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;ch4&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;sample&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;dcOffset&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;sample&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;p&gt;And the spikes went away!  As an added bonus, waveforms are no longer upside-down!  Now all I need is to follow the steps&amp;nbsp;above:&lt;/p&gt;
&lt;table class="highlighttable"&gt;&lt;tr&gt;&lt;td class="linenos"&gt;&lt;div class="linenodiv"&gt;&lt;pre&gt;&lt;span class="normal"&gt; 1&lt;/span&gt;
&lt;span class="normal"&gt; 2&lt;/span&gt;
&lt;span class="normal"&gt; 3&lt;/span&gt;
&lt;span class="normal"&gt; 4&lt;/span&gt;
&lt;span class="normal"&gt; 5&lt;/span&gt;
&lt;span class="normal"&gt; 6&lt;/span&gt;
&lt;span class="normal"&gt; 7&lt;/span&gt;
&lt;span class="normal"&gt; 8&lt;/span&gt;
&lt;span class="normal"&gt; 9&lt;/span&gt;
&lt;span class="normal"&gt;10&lt;/span&gt;
&lt;span class="normal"&gt;11&lt;/span&gt;
&lt;span class="normal"&gt;12&lt;/span&gt;
&lt;span class="normal"&gt;13&lt;/span&gt;
&lt;span class="normal"&gt;14&lt;/span&gt;
&lt;span class="normal"&gt;15&lt;/span&gt;
&lt;span class="normal"&gt;16&lt;/span&gt;
&lt;span class="normal"&gt;17&lt;/span&gt;
&lt;span class="normal"&gt;18&lt;/span&gt;
&lt;span class="normal"&gt;19&lt;/span&gt;
&lt;span class="normal"&gt;20&lt;/span&gt;
&lt;span class="normal"&gt;21&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/td&gt;&lt;td class="code"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;    &lt;span class="c1"&gt;; Re-enable the channel&lt;/span&gt;
    &lt;span class="ow"&gt;ld&lt;/span&gt; &lt;span class="nb"&gt;a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mh"&gt;$80&lt;/span&gt;
    &lt;span class="ow"&gt;ldh&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;rAUD3ENA&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="nb"&gt;a&lt;/span&gt;
    &lt;span class="c1"&gt;; Unplug it&lt;/span&gt;
    &lt;span class="ow"&gt;ld&lt;/span&gt; &lt;span class="nb"&gt;a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;rAUDTERM&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="ow"&gt;ld&lt;/span&gt; &lt;span class="nb"&gt;d&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;a&lt;/span&gt;
    &lt;span class="ow"&gt;and&lt;/span&gt; &lt;span class="nb"&gt;a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mb"&gt;%10111011&lt;/span&gt;
    &lt;span class="ow"&gt;ldh&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;rAUDTERM&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="nb"&gt;a&lt;/span&gt;
    &lt;span class="c1"&gt;; Set to a high frequency and start playing&lt;/span&gt;
    &lt;span class="ow"&gt;ld&lt;/span&gt; &lt;span class="nb"&gt;a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;LOW&lt;/span&gt;&lt;span class="err"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;2032&lt;/span&gt;&lt;span class="err"&gt;)&lt;/span&gt;
    &lt;span class="ow"&gt;ldh&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;rAUD3LOW&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="nb"&gt;a&lt;/span&gt;
    &lt;span class="ow"&gt;ld&lt;/span&gt; &lt;span class="nb"&gt;a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mh"&gt;$80&lt;/span&gt; &lt;span class="err"&gt;|&lt;/span&gt; &lt;span class="nv"&gt;HIGH&lt;/span&gt;&lt;span class="err"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;2032&lt;/span&gt;&lt;span class="err"&gt;)&lt;/span&gt;
    &lt;span class="ow"&gt;ldh&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;rAUD3HIGH&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="nb"&gt;a&lt;/span&gt;
    &lt;span class="c1"&gt;; Correct the frequency and restart&lt;/span&gt;
    &lt;span class="ow"&gt;ld&lt;/span&gt; &lt;span class="nb"&gt;a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;LOW&lt;/span&gt;&lt;span class="err"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;CH3_FREQUENCY&lt;/span&gt;&lt;span class="err"&gt;)&lt;/span&gt;
    &lt;span class="ow"&gt;ldh&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;rAUD3LOW&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="nb"&gt;a&lt;/span&gt;
    &lt;span class="ow"&gt;ld&lt;/span&gt; &lt;span class="nb"&gt;a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mh"&gt;$80&lt;/span&gt; &lt;span class="err"&gt;|&lt;/span&gt; &lt;span class="nv"&gt;HIGH&lt;/span&gt;&lt;span class="err"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;CH3_FREQUENCY&lt;/span&gt;&lt;span class="err"&gt;)&lt;/span&gt;
    &lt;span class="ow"&gt;ldh&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;rAUD3HIGH&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="nb"&gt;a&lt;/span&gt;
    &lt;span class="c1"&gt;; Plug back in&lt;/span&gt;
    &lt;span class="ow"&gt;ld&lt;/span&gt; &lt;span class="nb"&gt;a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;d&lt;/span&gt;
    &lt;span class="ow"&gt;ld&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;rAUDTERM&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="nb"&gt;a&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;p&gt;The result is so, so beautiful, &lt;em&gt;and&lt;/em&gt; virtually identical in every&amp;nbsp;emulator:&lt;/p&gt;
&lt;p&gt;&lt;audio controls src="https://eev.ee/media/cheezball/05k-aowr-success.wav"&gt;&lt;/audio&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;&lt;em&gt;&lt;span class="caps"&gt;YES&lt;/span&gt;!!!&lt;/em&gt;&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;I &lt;em&gt;think&lt;/em&gt; this problem was never noticeable on most games because it only ever adds a constant bias (based on the number of active channels, which would usually change infrequently), and a constant shift up or down sounds almost the same.  I&amp;#8217;ve sent a &lt;span class="caps"&gt;PR&lt;/span&gt; to mGBA, and hopefully I&amp;#8217;ll have understood this correctly.  If not, well, back to&amp;nbsp;despair.&lt;/p&gt;
&lt;p&gt;Wow!  I did it.  Cool.  It took a mighty long time to get this working, and now I feel a bit silly since I don&amp;#8217;t have an inventory or even an audio engine to really plug this into yet.  I also want to capture a much more pestful meow from Anise, which is a little difficult since he only does his loudest meowing when we&amp;#8217;re not around.  This is fantastic progress,&amp;nbsp;though!&lt;/p&gt;
&lt;h2 id="update-ii-dramatic-un-turnaround"&gt;&lt;a class="toclink" href="#update-ii-dramatic-un-turnaround"&gt;UPDATE II: DRAMATIC&amp;nbsp;UN-TURNAROUND&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;In the days since the above &amp;#8220;discovery&amp;#8221; I finally got ahold of a flash cart, which lets me test on real hardware.  I have unfortunate news: &lt;em&gt;I was totally wrong&lt;/em&gt;.  The weird thing mGBA does is in fact correct, and &amp;#8220;fixing&amp;#8221; it breaks a few other games that play custom sounds by &lt;em&gt;changing the master volume while nothing is playing&lt;/em&gt;.  So, of course, my &amp;#8220;successful&amp;#8221; attempt &lt;em&gt;still&lt;/em&gt; spikes on real&amp;nbsp;hardware.&lt;/p&gt;
&lt;p&gt;After all that, I&amp;#8217;m &lt;em&gt;still&lt;/em&gt; at square one.  I do have a couple leads: there&amp;#8217;s the master volume trick, which I don&amp;#8217;t quite understand yet, and there&amp;#8217;s also Duke Nukem for &lt;span class="caps"&gt;GBC&lt;/span&gt;, which uses my &lt;em&gt;original&lt;/em&gt; approach but quantizes the sounds more harshly which helps to disguise the spiking.  I also have a list of other games that use custom samples, and when I revisit this, I might just run through as many of them as possible and try to figure out exactly what they&amp;#8217;re&amp;nbsp;doing.&lt;/p&gt;
&lt;p&gt;But for now, this is shelved.&amp;nbsp;Again.&lt;/p&gt;
&lt;h2 id="to-be-continued"&gt;&lt;a class="toclink" href="#to-be-continued"&gt;To be&amp;nbsp;continued&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;This work doesn&amp;#8217;t correspond to a commit at all; it exists only as a local stash.  I&amp;#8217;ll clean it up later, once I figure out what to actually do with&amp;nbsp;it.&lt;/p&gt;
&lt;p&gt;Next time: &lt;em&gt;dialogue&lt;/em&gt;!  With moderately less suffering along the&amp;nbsp;way!&lt;/p&gt;
&lt;script type="text/javascript"&gt;if (!document.getElementById('mathjaxscript_pelican_#%@#$@#')) {
    var align = "center",
        indent = "0em",
        linebreak = "false";

    if (false) {
        align = (screen.width &lt; 768) ? "left" : align;
        indent = (screen.width &lt; 768) ? "0em" : indent;
        linebreak = (screen.width &lt; 768) ? 'true' : linebreak;
    }

    var mathjaxscript = document.createElement('script');
    mathjaxscript.id = 'mathjaxscript_pelican_#%@#$@#';
    mathjaxscript.type = 'text/javascript';
    mathjaxscript.src = 'https://cdnjs.cloudflare.com/ajax/libs/mathjax/2.7.3/latest.js?config=TeX-AMS-MML_HTMLorMML';

    var configscript = document.createElement('script');
    configscript.type = 'text/x-mathjax-config';
    configscript[(window.opera ? "innerHTML" : "text")] =
        "MathJax.Hub.Config({" +
        "    config: ['MMLorHTML.js']," +
        "    TeX: { extensions: ['AMSmath.js','AMSsymbols.js','noErrors.js','noUndefined.js'], equationNumbers: { autoNumber: 'none' } }," +
        "    jax: ['input/TeX','input/MathML','output/HTML-CSS']," +
        "    extensions: ['tex2jax.js','mml2jax.js','MathMenu.js','MathZoom.js']," +
        "    displayAlign: '"+ align +"'," +
        "    displayIndent: '"+ indent +"'," +
        "    showMathMenu: true," +
        "    messageStyle: 'normal'," +
        "    tex2jax: { " +
        "        inlineMath: [ ['\\\\(','\\\\)'] ], " +
        "        displayMath: [ ['$$','$$'] ]," +
        "        processEscapes: true," +
        "        preview: 'TeX'," +
        "    }, " +
        "    'HTML-CSS': { " +
        "        availableFonts: ['STIX', 'TeX']," +
        "        preferredFont: 'STIX'," +
        "        styles: { '.MathJax_Display, .MathJax .mo, .MathJax .mi, .MathJax .mn': {color: 'inherit ! important'} }," +
        "        linebreaks: { automatic: "+ linebreak +", width: '90% container' }," +
        "    }, " +
        "}); " +
        "if ('default' !== 'default') {" +
            "MathJax.Hub.Register.StartupHook('HTML-CSS Jax Ready',function () {" +
                "var VARIANT = MathJax.OutputJax['HTML-CSS'].FONTDATA.VARIANT;" +
                "VARIANT['normal'].fonts.unshift('MathJax_default');" +
                "VARIANT['bold'].fonts.unshift('MathJax_default-bold');" +
                "VARIANT['italic'].fonts.unshift('MathJax_default-italic');" +
                "VARIANT['-tex-mathit'].fonts.unshift('MathJax_default-italic');" +
            "});" +
            "MathJax.Hub.Register.StartupHook('SVG Jax Ready',function () {" +
                "var VARIANT = MathJax.OutputJax.SVG.FONTDATA.VARIANT;" +
                "VARIANT['normal'].fonts.unshift('MathJax_default');" +
                "VARIANT['bold'].fonts.unshift('MathJax_default-bold');" +
                "VARIANT['italic'].fonts.unshift('MathJax_default-italic');" +
                "VARIANT['-tex-mathit'].fonts.unshift('MathJax_default-italic');" +
            "});" +
        "}";

    (document.body || document.getElementsByTagName('head')[0]).appendChild(configscript);
    (document.body || document.getElementsByTagName('head')[0]).appendChild(mathjaxscript);
}
&lt;/script&gt;</content><category term="blog"></category><category term="tech"></category><category term="gamedev"></category><category term="cheezball rising"></category></entry><entry><title>Cheezball Rising: Maps and sprites</title><link href="https://eev.ee/blog/2018/07/15/cheezball-rising-maps-and-sprites/" rel="alternate"></link><published>2018-07-15T18:33:00-07:00</published><updated>2018-07-15T18:33:00-07:00</updated><author><name>Eevee</name></author><id>tag:eev.ee,2018-07-15:/blog/2018/07/15/cheezball-rising-maps-and-sprites/</id><summary type="html">&lt;p&gt;This is a series about &lt;a href="https://github.com/eevee/anise-cheezball-rising"&gt;&lt;strong&gt;Star Anise Chronicles: Cheezball Rising&lt;/strong&gt;&lt;/a&gt;, an expansive adventure game about my cat for the Game Boy Color.  Follow along as I struggle to make something with this bleeding-edge console!&lt;/p&gt;
&lt;p&gt;GitHub has &lt;a href="https://github.com/eevee/anise-cheezball-rising/releases"&gt;intermittent prebuilt ROMs&lt;/a&gt;, or you can get them a week early &lt;a href="https://www.patreon.com/eevee/posts?tag=cheezball%20rising"&gt;on Patreon&lt;/a&gt; if you pledge $4.  More details in the &lt;a href="https://github.com/eevee/anise-cheezball-rising"&gt;&lt;span class="caps"&gt;README&lt;/span&gt;&lt;/a&gt;!&lt;/p&gt;
&lt;hr/&gt;
&lt;p&gt;In this issue, I get a little asset pipeline working and finally have a real map.&lt;/p&gt;
&lt;p&gt;Previously: &lt;a href="https://eev.ee/blog/2018/07/13/cheezball-rising-spring-cleaning/"&gt;spring cleaning&lt;/a&gt;.&lt;br/&gt;
Next: &lt;a href="https://eev.ee/blog/2018/09/06/cheezball-rising-resounding-failure/"&gt;resounding &lt;s&gt;failure&lt;/s&gt; success?&lt;/a&gt;.&lt;/p&gt;
</summary><content type="html">&lt;p&gt;This is a series about &lt;a href="https://github.com/eevee/anise-cheezball-rising"&gt;&lt;strong&gt;Star Anise Chronicles: Cheezball Rising&lt;/strong&gt;&lt;/a&gt;, an expansive adventure game about my cat for the Game Boy Color.  Follow along as I struggle to make something with this bleeding-edge&amp;nbsp;console!&lt;/p&gt;
&lt;p&gt;GitHub has &lt;a href="https://github.com/eevee/anise-cheezball-rising/releases"&gt;intermittent prebuilt ROMs&lt;/a&gt;, or you can get them a week early &lt;a href="https://www.patreon.com/eevee/posts?tag=cheezball%20rising"&gt;on Patreon&lt;/a&gt; if you pledge $4.  More details in the &lt;a href="https://github.com/eevee/anise-cheezball-rising"&gt;&lt;span class="caps"&gt;README&lt;/span&gt;&lt;/a&gt;!&lt;/p&gt;
&lt;hr /&gt;
&lt;p&gt;In this issue, I get a little asset pipeline working and finally have a real&amp;nbsp;map.&lt;/p&gt;
&lt;p&gt;Previously: &lt;a href="https://eev.ee/blog/2018/07/13/cheezball-rising-spring-cleaning/"&gt;spring cleaning&lt;/a&gt;.&lt;br /&gt;
Next: &lt;a href="https://eev.ee/blog/2018/09/06/cheezball-rising-resounding-failure/"&gt;resounding &lt;s&gt;failure&lt;/s&gt; success?&lt;/a&gt;.&lt;/p&gt;


&lt;h2 id="recap"&gt;&lt;a class="toclink" href="#recap"&gt;Recap&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;The last post only covered some minor problems (including, I grant you, being &lt;em&gt;totally broken&lt;/em&gt;), so the current state of the game is basically unchanged from&amp;nbsp;before.&lt;/p&gt;
&lt;div class="prose-full-illustration"&gt;
&lt;img src="https://eev.ee/media/cheezball/02d-anise-move-grass.gif" alt="A space cat roams around on a grassy background"&gt;
&lt;/div&gt;

&lt;p&gt;That grass pattern, the grass sprite itself, and the color scheme are all hardcoded — written directly into the source code, by hand.  If this game is going to get very far at all, I urgently need a better way to inject some&amp;nbsp;art.&lt;/p&gt;
&lt;h2 id="constraints"&gt;&lt;a class="toclink" href="#constraints"&gt;Constraints&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;The Game Boy imposes some fairly harsh constraints on the artwork — which is part of the charm!  But now I have to figure out how to work within those constraints most effectively.  Here&amp;#8217;s what I&amp;#8217;ve got to work&amp;nbsp;with.&lt;/p&gt;
&lt;p&gt;Bear in mind that I intend for the game to be based around 16×16, um, tiles.  Okay, it&amp;#8217;s extremely confusing that &amp;#8220;tile&amp;#8221; might refer either to the base size of the artwork &lt;em&gt;or&lt;/em&gt; to the Game Boy&amp;#8217;s native 8×8 tiles, so I&amp;#8217;m going to call the art &lt;em&gt;tiles&lt;/em&gt; and the Game Boy&amp;#8217;s basic unit a &lt;em&gt;character&lt;/em&gt; (which is what the manual&amp;nbsp;does).&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;The background layer is a grid of 8×8 characters, each of which uses one of eight 4-color background&amp;nbsp;palettes.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;The object layer is a set of 8×16 character pairs, each of which uses one of eight 3-color object palettes.  These palettes are 3-color because color 0 is always&amp;nbsp;transparent.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;No more than 40 objects can appear on screen at the same time.  (There &lt;em&gt;is&lt;/em&gt; a way to weasel past this limit, but it requires considerable&amp;nbsp;trickery.)&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;No more than 10 objects can appear in the same row of pixels.  (I believe this is a hard&amp;nbsp;limit.)&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;There are three blocks of 256 chars each.  I can divide this between the background and objects more or less however I want, though neither can have more than two blocks (= 512&amp;nbsp;chars).&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;I&amp;#8217;m intending for the game to be based around a 16×16 grid, a fairly common size for the Game Boy.  That makes me a &lt;em&gt;little&lt;/em&gt; concerned about the per-row object limit — each entity will need to have &lt;em&gt;two&lt;/em&gt; Game Boy objects side by side, so I&amp;#8217;m really limited to only &lt;em&gt;five&lt;/em&gt; entities sharing the same row of pixels.  I can&amp;#8217;t do much about that quite yet (and only have one entity anyway), but it&amp;#8217;s likely to affect how I design maps and draw&amp;nbsp;sprites.&lt;/p&gt;
&lt;p&gt;The next biggest problem is &lt;em&gt;colors&lt;/em&gt;.  Each object palette can only have three colors, which in practice means a shadow/outline color, a highlight color, and a base color.  This is why every &lt;span class="caps"&gt;NPC&lt;/span&gt; and overworld critter in Pokémon &lt;span class="caps"&gt;GSC&lt;/span&gt; and the Zeldas is basically monochromatic.  They pull it off &lt;a href="https://www.spriters-resource.com/game_boy_gbc/pokemoncrystal/sheet/65561/"&gt;really well&lt;/a&gt; by making very effective use of the highlight and shadow&amp;nbsp;colors.&lt;/p&gt;
&lt;p&gt;Since 16×16 sprites are composed of multiple Game Boy objects, it&amp;#8217;s possible to overcome this limit by giving each part of the sprite a different palette.  Unfortunately, objects being 8×16 means the sprites are split &lt;em&gt;vertically&lt;/em&gt;, when it would be most useful to have different colors for e.g. the head and body.  I wish the Game Boy supported 16×8 objects!  That&amp;#8217;d help a ton with the per-row limit, too.  Alas, a few decades too late to change it&amp;nbsp;now.&lt;/p&gt;
&lt;hr /&gt;
&lt;p&gt;As for the &lt;em&gt;number&lt;/em&gt; of chars…  well, let&amp;#8217;s see.  The whole screen is only 160×144, which is 20×18 or 360 chars, so I could allocate two blocks to the background and have 512 — more than enough to cover the entire screen in unique chars!  (I expect one block to be more than enough for objects, since I can only show 80 object chars at once&amp;nbsp;anyway.)&lt;/p&gt;
&lt;p&gt;On the other hand, I&amp;#8217;ll need to reserve some of that space for text and &lt;span class="caps"&gt;UI&lt;/span&gt; and whatnot, and each 16×16 tile is composed of &lt;em&gt;four&lt;/em&gt; chars.  If I very generously allocate a whole block to window dressing (enough for all of &lt;span class="caps"&gt;ISO&lt;/span&gt;-8859-1?), that leaves 256 chars, which is 64 tiles, which is a tileset that fits in an eight-by-eight&amp;nbsp;square.&lt;/p&gt;
&lt;p&gt;For comparison&amp;#8217;s sake, even fox flux&amp;#8217;s &lt;a href="https://github.com/eevee/fox-flux/blob/v0.1.3/assets/images/terrain.png"&gt;relatively limited tileset&lt;/a&gt; is a sixteen-tile square — four times as big.  This feels a little&amp;nbsp;dire.&lt;/p&gt;
&lt;p&gt;But how can it be dire, when I have enough sprite space to fill the screen and then&amp;nbsp;some?&lt;/p&gt;
&lt;p&gt;Let&amp;#8217;s see here.  A pretty good chunk of the fox flux tileset is unused or outright blank.  Some of these tiles are art for moving objects that happened to fit in the grid, and those wouldn&amp;#8217;t be in the background tileset.  And while all of the tiles are distinct, a lot of the basic terrain has some significant&amp;nbsp;overlap:&lt;/p&gt;
&lt;div class="prose-full-illustration"&gt;
&lt;img src="https://eev.ee/media/cheezball/04a-fox-flux-duplicates.png" alt="A set of dirt tiles from fox flux, colored to indicate where different tiles have identical corners"&gt;
&lt;/div&gt;

&lt;p&gt;All of the regions of the same color are identical.  These 9 distinct tiles could fit into 20 chars if they shared the common parts, rather than the 36 required to naïvely cutting each one into four dedicated&amp;nbsp;chars.&lt;/p&gt;
&lt;p&gt;(The fox flux grid is 32×32, so everything is twice as big as it will be on the Game Boy, but you get the&amp;nbsp;idea.)&lt;/p&gt;
&lt;p&gt;I&amp;#8217;m feeling a little better about this, especially knowing I &lt;em&gt;do&lt;/em&gt; have enough space to cover the whole screen.  Worst case, I could draw the map as though it were a single bitmap.  I don&amp;#8217;t want to have to rely on that if I can get away with it, though — I suspect I&amp;#8217;d need to constantly load chars on the fly, and copying stuff around eats into my &lt;span class="caps"&gt;CPU&lt;/span&gt; budget surprisingly&amp;nbsp;quickly.&lt;/p&gt;
&lt;h3 id="research"&gt;&lt;a class="toclink" href="#research"&gt;Research&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;That does get me wondering: what, exactly, &lt;em&gt;do&lt;/em&gt; the Oracle games do?  I haven&amp;#8217;t done any precise measurements, but I&amp;#8217;m pretty sure they have more than sixty-four distinct map tiles throughout their large connected worlds.  Let&amp;#8217;s have a&amp;nbsp;look!&lt;/p&gt;
&lt;div class="prose-full-illustration"&gt;
&lt;img src="https://eev.ee/media/cheezball/04b-oracle-of-ages-graveyard.png" alt="Oracle of Ages and its live tilemap, in the graveyard, showing the graveyard tileset"&gt;
&lt;/div&gt;

&lt;p&gt;Here I am in the graveyard near the start of Oracle of Ages.  The &amp;#8220;creepy tree&amp;#8221; here is distinct and doesn&amp;#8217;t really appear anywhere else, so I found it in the tile viewer (lower right) and will be keeping an eye on it.  Note that only the left half of the face is visible; the right half is using the same tiles, flipped horizontally.  (The colors are different because the tile viewer shows the literal colors, whereas the game itself is being drawn with a&amp;nbsp;shader.)&lt;/p&gt;
&lt;p&gt;Let&amp;#8217;s walk left one&amp;nbsp;screen.&lt;/p&gt;
&lt;div class="prose-full-illustration"&gt;
&lt;img src="https://eev.ee/media/cheezball/04c-oracle-of-ages-graveyard-entrance.png" alt="Oracle of Ages and its live tilemap, outside of the graveyard"&gt;
&lt;/div&gt;

&lt;p&gt;Now, this is interesting.  The creepy tree is still on the screen here, so its tiles are naturally still loaded.  But a bunch of tiles on the left — parts of the dungeon entrance and other graveyard things — have been replaced by &lt;em&gt;town&lt;/em&gt; tiles.  I&amp;#8217;m several screens away from the&amp;nbsp;town!&lt;/p&gt;
&lt;p&gt;The next screen up has no creepy trees, but its tiles remain.  Of course, they&amp;#8217;d &lt;em&gt;have&lt;/em&gt; to, since the creepy tree is still visible during a transition.  I have to go left from there before the tree&amp;nbsp;disappears:&lt;/p&gt;
&lt;div class="prose-full-illustration"&gt;
&lt;img src="https://eev.ee/media/cheezball/04d-oracle-of-ages-shop-tiles-loaded.png" alt="Oracle of Ages and its live tilemap, with tiles spelling SHOP clearly visible"&gt;
&lt;/div&gt;

&lt;p&gt;Wow!  At a glance, this looks like enough tiles to draw the entire&amp;nbsp;town.&lt;/p&gt;
&lt;p&gt;This is fascinating.  The Oracle games have several transitions between major areas, marked by fade-outs or palette changes — the purple-tinted graveyard is an obvious example.  But it looks like there are also &lt;em&gt;minor&lt;/em&gt; transitions that update the tileset while I&amp;#8217;m still several screens away from where those tiles are used.  The screens around the transition only use common tiles like grass and regular trees, so I never notice anything is&amp;nbsp;happening.&lt;/p&gt;
&lt;p&gt;That&amp;#8217;s cute, clever, and an easy way to make screen transitions work without having to figure out what tiles are becoming unused as they slide off the&amp;nbsp;screen!&lt;/p&gt;
&lt;p&gt;At this point I realize I may be getting ahead of myself.  Screen transitions?  I don&amp;#8217;t have a map yet!  Hell, I don&amp;#8217;t even have a camera.  Time to back up and make something I can build&amp;nbsp;on.&lt;/p&gt;
&lt;h2 id="designing-a-tileset"&gt;&lt;a class="toclink" href="#designing-a-tileset"&gt;Designing a&amp;nbsp;tileset&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;I&amp;#8217;m pretty tired of manually translating art into bits.  It&amp;#8217;s 2018, dammit.  I want to use all the regular tools I would use for this, I want the Game Boy&amp;#8217;s limitations to be expressed as simply as possible, and I want minimal friction between the source artwork and the&amp;nbsp;game.&lt;/p&gt;
&lt;p&gt;Here&amp;#8217;s my idea.  I know I only have 8 palettes to work with, so I&amp;#8217;m decreeing that tilesets will be stored as paletted PNGs.  The first four colors in the image palette will become the first Game Boy palette; the next four colors become the second Game Boy palette; and so on.  If I then resize Aseprite&amp;#8217;s palette panel to be four colors wide, I&amp;#8217;ll have an instant view of all my available combinations of&amp;nbsp;colors.&lt;/p&gt;
&lt;p&gt;This already has some problems — for starters, if the same color appears in multiple palettes (which will almost certainly happen, for the sake of cohesion), I&amp;#8217;m very likely to confuse the hell out of myself.  I also have &lt;em&gt;no idea&lt;/em&gt; how to extend this into multiple tilesets, but for now I&amp;#8217;ll pretend the entire game world only uses a single&amp;nbsp;tileset.&lt;/p&gt;
&lt;p&gt;I could instead dynamically infer the palettes based on what combinations of colors are actually used, but after more than a couple tiles, it would be a nightmare for a human to keep track of what those combinations are.  With this approach, all a human needs to do is color-drop a pixel from a particular tile and look at what row the color&amp;#8217;s&amp;nbsp;in.&lt;/p&gt;
&lt;p&gt;After a quick jaunt into the pixel mines, here are some&amp;nbsp;tiles.&lt;/p&gt;
&lt;div class="prose-full-illustration"&gt;
&lt;img src="https://eev.ee/media/cheezball/04e-moon-tileset.png" alt="A small set of pastel yellow moon tiles"&gt;
&lt;/div&gt;

&lt;p&gt;Or, as viewed in&amp;nbsp;Aseprite:&lt;/p&gt;
&lt;div class="prose-full-illustration"&gt;
&lt;img src="https://eev.ee/media/cheezball/04f-moon-tileset-aseprite.png" alt="The same set of tiles, as seen in an editor, with the four-color palette visible"&gt;
&lt;/div&gt;

&lt;p&gt;That&amp;#8217;s only one palette, but hopefully you can see what I&amp;#8217;m going for here.  It&amp;#8217;s enough to get&amp;nbsp;started.&lt;/p&gt;
&lt;p&gt;At this point, I started writing a little Python script that used Pillow to inspect the colors and pixels and dump them out to rgbasm-flavored source code.  The script itself is not especially interesting: run through each 8×8 block of pixels, look at each pixel&amp;#8217;s palette index, mod 4 to get the index within the Game Boy palette, print out as backtick literals.  (I could spit out raw binary data, but I wanted to be able to inspect the intermediate form easily.  Maybe&amp;nbsp;later.)&lt;/p&gt;
&lt;p&gt;The&amp;nbsp;results:&lt;/p&gt;
&lt;table class="highlighttable"&gt;&lt;tr&gt;&lt;td class="linenos"&gt;&lt;div class="linenodiv"&gt;&lt;pre&gt;&lt;span class="normal"&gt; 1&lt;/span&gt;
&lt;span class="normal"&gt; 2&lt;/span&gt;
&lt;span class="normal"&gt; 3&lt;/span&gt;
&lt;span class="normal"&gt; 4&lt;/span&gt;
&lt;span class="normal"&gt; 5&lt;/span&gt;
&lt;span class="normal"&gt; 6&lt;/span&gt;
&lt;span class="normal"&gt; 7&lt;/span&gt;
&lt;span class="normal"&gt; 8&lt;/span&gt;
&lt;span class="normal"&gt; 9&lt;/span&gt;
&lt;span class="normal"&gt;10&lt;/span&gt;
&lt;span class="normal"&gt;11&lt;/span&gt;
&lt;span class="normal"&gt;12&lt;/span&gt;
&lt;span class="normal"&gt;13&lt;/span&gt;
&lt;span class="normal"&gt;14&lt;/span&gt;
&lt;span class="normal"&gt;15&lt;/span&gt;
&lt;span class="normal"&gt;16&lt;/span&gt;
&lt;span class="normal"&gt;17&lt;/span&gt;
&lt;span class="normal"&gt;18&lt;/span&gt;
&lt;span class="normal"&gt;19&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/td&gt;&lt;td class="code"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="k"&gt;SECTION &amp;quot;Map dumping test&amp;quot;, ROM0&lt;/span&gt;
&lt;span class="nf"&gt;TEST_PALETTES:&lt;/span&gt;
    &lt;span class="k"&gt;dw %0101011110111101&lt;/span&gt;
    &lt;span class="k"&gt;dw %0101011100011110&lt;/span&gt;
    &lt;span class="k"&gt;dw %0100101010111100&lt;/span&gt;
    &lt;span class="k"&gt;dw %0100011001111000&lt;/span&gt;
    &lt;span class="c1"&gt;; ... enough zeroes to make eight palettes ...&lt;/span&gt;
&lt;span class="c1"&gt;; sorry, in the script I was calling them &amp;quot;tiles&amp;quot;, not &amp;quot;chars&amp;quot;&lt;/span&gt;
&lt;span class="nf"&gt;TEST_TILES:&lt;/span&gt;
    &lt;span class="c1"&gt;; tile 0 at 0, 0&lt;/span&gt;
    &lt;span class="k"&gt;dw `00001000&lt;/span&gt;
    &lt;span class="k"&gt;dw `00000000&lt;/span&gt;
    &lt;span class="k"&gt;dw `00100000&lt;/span&gt;
    &lt;span class="k"&gt;dw `00000000&lt;/span&gt;
    &lt;span class="k"&gt;dw `00000000&lt;/span&gt;
    &lt;span class="k"&gt;dw `00000000&lt;/span&gt;
    &lt;span class="k"&gt;dw `20000000&lt;/span&gt;
    &lt;span class="k"&gt;dw `20000002&lt;/span&gt;
    &lt;span class="c1"&gt;; ... etc ...&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;p&gt;And hey, I already &lt;em&gt;have&lt;/em&gt; code that can load palettes and chars, so all I have to do is swap out the old labels for these&amp;nbsp;ones.&lt;/p&gt;
&lt;p&gt;Now I have a tileset I can load into the game, which is very exciting, except that I can&amp;#8217;t see any of them because I still don&amp;#8217;t have a map.  I could draw a test map by hand, I suppose, but the whole point of this exercise was to avoid ever doing that&amp;nbsp;again.&lt;/p&gt;
&lt;h2 id="drawing-a-map"&gt;&lt;a class="toclink" href="#drawing-a-map"&gt;Drawing a&amp;nbsp;map&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;In keeping with the &amp;#8220;it&amp;#8217;s 2018 dammit&amp;#8221; approach, I elect to use &lt;a href="https://www.mapeditor.org/"&gt;Tiled&lt;/a&gt; for drawing the maps.  I&amp;#8217;ve used it for several LÖVE games, and while its general-purposeness makes it a little clumsy at times, it&amp;#8217;s flexible enough to express basically&amp;nbsp;anything.&lt;/p&gt;
&lt;p&gt;I make a tileset and create a map.  I choose 256×256 pixels (16×16 tiles), the same size as the Game Boy screen buffer, and fill it with arbitrary terrain.  In retrospect, I probably should&amp;#8217;ve made it the size of the &lt;em&gt;screen&lt;/em&gt;, since I still don&amp;#8217;t have a camera.  Oh,&amp;nbsp;well.&lt;/p&gt;
&lt;p&gt;Here, I hit a minor roadblock.  I want to do as much work as possible upfront, so I want to store the map in the &lt;span class="caps"&gt;ROM&lt;/span&gt; as chars, not tiles.  That means I need to know what chars make up each tile, which is determined by the script that converts the image to char data.  Multiple maps might use the same tileset, and a map might use multiple tilesets, so it seems like I&amp;#8217;ll need some intermediate build assets with this&amp;nbsp;information…&lt;/p&gt;
&lt;p&gt;(In retrospect again, I realize that the game may need to know about tiles rather than just chars, since there&amp;#8217;ll surely be at least a few map tiles that act like entities — switches and the like — and those need to function as single units.  I guess I&amp;#8217;ll work that out&amp;nbsp;later.)&lt;/p&gt;
&lt;p&gt;This is all looking like an awful lot of messing around (and a lot of potential points of failure) before I can get anything on the dang screen.  I waffle for a bit, then decide to start with a single step that simultaneously dumps the tiles &lt;em&gt;and&lt;/em&gt; the map.  I can split it up when I actually have more than one of&amp;nbsp;either.&lt;/p&gt;
&lt;p&gt;You can check out &lt;a href="https://github.com/eevee/anise-cheezball-rising/blob/a2204f05378cee6faa640854814038c1012ab4f5/util/png-to-tiles.py"&gt;the resulting script&lt;/a&gt; if you like, but again, I don&amp;#8217;t think it&amp;#8217;s particularly interesting.  It enforces a few more constraints than before, and adds a &lt;code&gt;TEST_MAP_1&lt;/code&gt; label containing all the char data, row by row.  Loading that into &lt;span class="caps"&gt;VRAM&lt;/span&gt; is almost comically&amp;nbsp;simple:&lt;/p&gt;
&lt;table class="highlighttable"&gt;&lt;tr&gt;&lt;td class="linenos"&gt;&lt;div class="linenodiv"&gt;&lt;pre&gt;&lt;span class="normal"&gt;1&lt;/span&gt;
&lt;span class="normal"&gt;2&lt;/span&gt;
&lt;span class="normal"&gt;3&lt;/span&gt;
&lt;span class="normal"&gt;4&lt;/span&gt;
&lt;span class="normal"&gt;5&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/td&gt;&lt;td class="code"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;    &lt;span class="c1"&gt;; Read from the test map&lt;/span&gt;
    &lt;span class="ow"&gt;ld&lt;/span&gt; &lt;span class="nb"&gt;hl&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mh"&gt;$9800&lt;/span&gt;
    &lt;span class="ow"&gt;ld&lt;/span&gt; &lt;span class="nb"&gt;de&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;TEST_MAP_1&lt;/span&gt;
    &lt;span class="ow"&gt;ld&lt;/span&gt; &lt;span class="nb"&gt;bc&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;1024&lt;/span&gt;
    &lt;span class="ow"&gt;call&lt;/span&gt; &lt;span class="nv"&gt;copy16&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;p&gt;The screen buffer is 32×32 chars, or 1024 bytes.  As you may suspect, &lt;code&gt;copy16&lt;/code&gt; is like &lt;code&gt;copy&lt;/code&gt;, but it takes a 16-bit count in &lt;code&gt;bc&lt;/code&gt;.&lt;/p&gt;
&lt;table class="highlighttable"&gt;&lt;tr&gt;&lt;td class="linenos"&gt;&lt;div class="linenodiv"&gt;&lt;pre&gt;&lt;span class="normal"&gt; 1&lt;/span&gt;
&lt;span class="normal"&gt; 2&lt;/span&gt;
&lt;span class="normal"&gt; 3&lt;/span&gt;
&lt;span class="normal"&gt; 4&lt;/span&gt;
&lt;span class="normal"&gt; 5&lt;/span&gt;
&lt;span class="normal"&gt; 6&lt;/span&gt;
&lt;span class="normal"&gt; 7&lt;/span&gt;
&lt;span class="normal"&gt; 8&lt;/span&gt;
&lt;span class="normal"&gt; 9&lt;/span&gt;
&lt;span class="normal"&gt;10&lt;/span&gt;
&lt;span class="normal"&gt;11&lt;/span&gt;
&lt;span class="normal"&gt;12&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/td&gt;&lt;td class="code"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="c1"&gt;; copy bc bytes from de to hl&lt;/span&gt;
&lt;span class="c1"&gt;; NOTE: bc must not be zero&lt;/span&gt;
&lt;span class="nf"&gt;copy16:&lt;/span&gt;
    &lt;span class="ow"&gt;ld&lt;/span&gt; &lt;span class="nb"&gt;a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;de&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="ow"&gt;inc&lt;/span&gt; &lt;span class="nb"&gt;de&lt;/span&gt;
    &lt;span class="ow"&gt;ld&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;hl&lt;/span&gt;&lt;span class="p"&gt;+],&lt;/span&gt; &lt;span class="nb"&gt;a&lt;/span&gt;
    &lt;span class="ow"&gt;dec&lt;/span&gt; &lt;span class="nb"&gt;bc&lt;/span&gt;
    &lt;span class="c1"&gt;; dec bc doesn&amp;#39;t set flags, so gotta check by hand&lt;/span&gt;
    &lt;span class="ow"&gt;ld&lt;/span&gt; &lt;span class="nb"&gt;a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;b&lt;/span&gt;
    &lt;span class="ow"&gt;or&lt;/span&gt; &lt;span class="nb"&gt;a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;c&lt;/span&gt;
    &lt;span class="ow"&gt;jr&lt;/span&gt; &lt;span class="nv"&gt;nz&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;copy16&lt;/span&gt;
    &lt;span class="ow"&gt;ret&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;p&gt;Hm.  It&amp;#8217;s a little harder to justify the &lt;code&gt;bc = 0&lt;/code&gt; case as a feature here, since that would try to overwrite every single byte in the entire address space.  Don&amp;#8217;t do that,&amp;nbsp;then.&lt;/p&gt;
&lt;div class="prose-full-illustration"&gt;
&lt;img src="https://eev.ee/media/cheezball/04g-moon-landing.png" alt="Anise, in-game, walking on the moon tiles"&gt;
&lt;/div&gt;

&lt;p&gt;Now, at long long last, I have a background with some actual art!  It&amp;#8217;s starting to feel like something!  I&amp;#8217;ve even got something resembling a&amp;nbsp;workflow.&lt;/p&gt;
&lt;div class="prose-full-illustration"&gt;
&lt;img src="https://eev.ee/media/cheezball/04h-mapping-workflow.png" alt="My desktop, showing the moon tiles in an image editor, the map put together in Tiled, and the game running in mGBA"&gt;
&lt;/div&gt;

&lt;p&gt;All in a day&amp;#8217;s work.  Good time to call it,&amp;nbsp;right?&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Except&lt;/em&gt;…&lt;/p&gt;
&lt;p&gt;I just wrote this char loading&amp;nbsp;code…&lt;/p&gt;
&lt;p&gt;And there&amp;#8217;s still &lt;em&gt;one thing&lt;/em&gt; still&amp;nbsp;hardcoded…&lt;/p&gt;
&lt;p&gt;I wonder if I could do something about&amp;nbsp;that…?&lt;/p&gt;
&lt;h2 id="sprites"&gt;&lt;a class="toclink" href="#sprites"&gt;Sprites&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Above, I conspicuously did not mention how I integrated the Python script into the build system.  And, well, I didn&amp;#8217;t do that.  I ran it manually and put it somewhere and committed it all as-is.  You currently (still!) can&amp;#8217;t actually build the game without repeating my steps.  You can&amp;#8217;t even just put the output in the right place, because you &lt;em&gt;also&lt;/em&gt; have to delete some debug output from the &lt;em&gt;middle of the file&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;It gets worse!  Here&amp;#8217;s&amp;nbsp;how.&lt;/p&gt;
&lt;p&gt;I have some Anise walking sprites, too, drawn in Aseprite.  They&amp;#8217;re pretty cute and I&amp;#8217;d love to have them in the game, now that I have some Real Art™ for the&amp;nbsp;background.&lt;/p&gt;
&lt;div class="prose-full-illustration"&gt;
&lt;img src="https://eev.ee/media/cheezball/04i-anise-walk-4x.gif" alt="Star Anise, walking forwards"&gt;
&lt;/div&gt;

&lt;p&gt;Why not throw these at the same script and hack them into&amp;nbsp;animating?&lt;/p&gt;
&lt;p&gt;Unfortunately, this introduces a bit of manual work, as animation often does.  (My kingdom for a way to embed a small simple animation in a larger spritesheet in Aseprite!)  I&amp;#8217;ve typically animated every critter in its own Aseprite file — or stacked several vertically in the same file when their animations are similar enough — and then exported as a sheet with the frames running off horizontally.  You can see this at work in fox flux, e.g. on its &lt;a href="https://github.com/eevee/fox-flux/blob/v0.1.3/assets/images/critters.png"&gt;critter sheet&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;But Star Anise introduces a wrinkle that prevents even that slightly clumsy workflow from&amp;nbsp;working.&lt;/p&gt;
&lt;p&gt;You &lt;em&gt;may&lt;/em&gt; have noticed that the walking sprite above blows the color budget considerably, using a whopping &lt;em&gt;five&lt;/em&gt; colors.  The secret is that Anise himself fits in a 16×16 square, and then his antenna is a &lt;em&gt;third&lt;/em&gt; 8×16 sprite drawn on top.  I can&amp;#8217;t simply export him as a spritesheet, because the antenna needs to be separate, and it&amp;#8217;s not even aligned to the grid.  It doesn&amp;#8217;t even stay in the same place&amp;nbsp;consistently!&lt;/p&gt;
&lt;p&gt;I could &lt;em&gt;maybe&lt;/em&gt; hack something together that would automatically pull the incompatible pixels into a separate sprite.  I might &lt;em&gt;need&lt;/em&gt; to, since — spoiler alert — there are an awful lot of Lunekos in this game.  For now, though, I did the dumbest thing that works and copied his frames to their own sheet by&amp;nbsp;hand.&lt;/p&gt;
&lt;div class="prose-full-illustration"&gt;
&lt;img src="https://eev.ee/media/cheezball/04j-anise-sheet.png" alt="Star Anise's walking frames laid out in a spritesheet"&gt;
&lt;/div&gt;

&lt;p&gt;The background is actually cyan, &lt;em&gt;not&lt;/em&gt; transparent.  I had to do this because my setup expects multiple sets of four colors — the first color in an object palette is still there, even if it&amp;#8217;s ignored — and only one color in an indexed &lt;span class="caps"&gt;PNG&lt;/span&gt; can be transparent.  (Don&amp;#8217;t @ me about &lt;span class="caps"&gt;PNG&lt;/span&gt; pixel formats.)  I could&amp;#8217;ve adjusted it to work with sets of three colors and put the transparent one at the end so the palette column trick still worked, but…  this was&amp;nbsp;easier.&lt;/p&gt;
&lt;p&gt;Here&amp;#8217;s the best part: I took the &lt;code&gt;main&lt;/code&gt; function from my tile loading script, copy-pasted it within the same file, and edited the &lt;em&gt;copy&lt;/em&gt; to dump these sprites sans map.  So now not only is there no build system, but half of the loading script is inaccessible!  Sorry.  We&amp;#8217;re getting into experiment territory and I am going to start making a lot of messes while I figure out what I actually&amp;nbsp;want.&lt;/p&gt;
&lt;p&gt;Using these within the game was just as easy as before — replace some labels with new ones — and the only real change was to use a third &lt;span class="caps"&gt;OAM&lt;/span&gt; slot for the antenna.  (The antenna has to appear &lt;em&gt;first&lt;/em&gt;; when sprites overlap, the one with the lowest index appears on&amp;nbsp;top.)&lt;/p&gt;
&lt;p&gt;That did make updating &lt;span class="caps"&gt;OAM&lt;/span&gt; a little clumsy; you may recall that before, I loaded the x and y positions into &lt;code&gt;b&lt;/code&gt; and &lt;code&gt;c&lt;/code&gt;, updated them, then wrote them back into &lt;span class="caps"&gt;OAM&lt;/span&gt;:&lt;/p&gt;
&lt;table class="highlighttable"&gt;&lt;tr&gt;&lt;td class="linenos"&gt;&lt;div class="linenodiv"&gt;&lt;pre&gt;&lt;span class="normal"&gt; 1&lt;/span&gt;
&lt;span class="normal"&gt; 2&lt;/span&gt;
&lt;span class="normal"&gt; 3&lt;/span&gt;
&lt;span class="normal"&gt; 4&lt;/span&gt;
&lt;span class="normal"&gt; 5&lt;/span&gt;
&lt;span class="normal"&gt; 6&lt;/span&gt;
&lt;span class="normal"&gt; 7&lt;/span&gt;
&lt;span class="normal"&gt; 8&lt;/span&gt;
&lt;span class="normal"&gt; 9&lt;/span&gt;
&lt;span class="normal"&gt;10&lt;/span&gt;
&lt;span class="normal"&gt;11&lt;/span&gt;
&lt;span class="normal"&gt;12&lt;/span&gt;
&lt;span class="normal"&gt;13&lt;/span&gt;
&lt;span class="normal"&gt;14&lt;/span&gt;
&lt;span class="normal"&gt;15&lt;/span&gt;
&lt;span class="normal"&gt;16&lt;/span&gt;
&lt;span class="normal"&gt;17&lt;/span&gt;
&lt;span class="normal"&gt;18&lt;/span&gt;
&lt;span class="normal"&gt;19&lt;/span&gt;
&lt;span class="normal"&gt;20&lt;/span&gt;
&lt;span class="normal"&gt;21&lt;/span&gt;
&lt;span class="normal"&gt;22&lt;/span&gt;
&lt;span class="normal"&gt;23&lt;/span&gt;
&lt;span class="normal"&gt;24&lt;/span&gt;
&lt;span class="normal"&gt;25&lt;/span&gt;
&lt;span class="normal"&gt;26&lt;/span&gt;
&lt;span class="normal"&gt;27&lt;/span&gt;
&lt;span class="normal"&gt;28&lt;/span&gt;
&lt;span class="normal"&gt;29&lt;/span&gt;
&lt;span class="normal"&gt;30&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/td&gt;&lt;td class="code"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;    &lt;span class="c1"&gt;; set b/c to the y/x coordinates&lt;/span&gt;
    &lt;span class="ow"&gt;ld&lt;/span&gt; &lt;span class="nb"&gt;hl&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;oam_buffer&lt;/span&gt;
    &lt;span class="ow"&gt;ld&lt;/span&gt; &lt;span class="nb"&gt;b&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;hl&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="ow"&gt;inc&lt;/span&gt; &lt;span class="nb"&gt;hl&lt;/span&gt;
    &lt;span class="ow"&gt;ld&lt;/span&gt; &lt;span class="nb"&gt;c&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;hl&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="ow"&gt;bit&lt;/span&gt; &lt;span class="nv"&gt;BUTTON_LEFT&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;a&lt;/span&gt;
    &lt;span class="ow"&gt;jr&lt;/span&gt; &lt;span class="nv"&gt;z&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="err"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;skip_left&lt;/span&gt;
    &lt;span class="ow"&gt;dec&lt;/span&gt; &lt;span class="nb"&gt;c&lt;/span&gt;
&lt;span class="nf"&gt;.skip_left:&lt;/span&gt;
    &lt;span class="ow"&gt;bit&lt;/span&gt; &lt;span class="nv"&gt;BUTTON_RIGHT&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;a&lt;/span&gt;
    &lt;span class="ow"&gt;jr&lt;/span&gt; &lt;span class="nv"&gt;z&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="err"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;skip_right&lt;/span&gt;
    &lt;span class="ow"&gt;inc&lt;/span&gt; &lt;span class="nb"&gt;c&lt;/span&gt;
&lt;span class="nf"&gt;.skip_right:&lt;/span&gt;
    &lt;span class="ow"&gt;bit&lt;/span&gt; &lt;span class="nv"&gt;BUTTON_UP&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;a&lt;/span&gt;
    &lt;span class="ow"&gt;jr&lt;/span&gt; &lt;span class="nv"&gt;z&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="err"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;skip_up&lt;/span&gt;
    &lt;span class="ow"&gt;dec&lt;/span&gt; &lt;span class="nb"&gt;b&lt;/span&gt;
&lt;span class="nf"&gt;.skip_up:&lt;/span&gt;
    &lt;span class="ow"&gt;bit&lt;/span&gt; &lt;span class="nv"&gt;BUTTON_DOWN&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;a&lt;/span&gt;
    &lt;span class="ow"&gt;jr&lt;/span&gt; &lt;span class="nv"&gt;z&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="err"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;skip_down&lt;/span&gt;
    &lt;span class="ow"&gt;inc&lt;/span&gt; &lt;span class="nb"&gt;b&lt;/span&gt;
&lt;span class="nf"&gt;.skip_down:&lt;/span&gt;
    &lt;span class="ow"&gt;ld&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;hl&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="nb"&gt;c&lt;/span&gt;
    &lt;span class="ow"&gt;dec&lt;/span&gt; &lt;span class="nb"&gt;hl&lt;/span&gt;
    &lt;span class="ow"&gt;ld&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;hl&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="nb"&gt;b&lt;/span&gt;
    &lt;span class="ow"&gt;ld&lt;/span&gt; &lt;span class="nb"&gt;a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;c&lt;/span&gt;
    &lt;span class="ow"&gt;add&lt;/span&gt; &lt;span class="nb"&gt;a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;8&lt;/span&gt;
    &lt;span class="ow"&gt;ld&lt;/span&gt; &lt;span class="nb"&gt;hl&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;oam_buffer&lt;/span&gt; &lt;span class="p"&gt;+&lt;/span&gt; &lt;span class="nv"&gt;5&lt;/span&gt;
    &lt;span class="ow"&gt;ld&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;hl&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="nb"&gt;a&lt;/span&gt;
    &lt;span class="ow"&gt;dec&lt;/span&gt; &lt;span class="nb"&gt;hl&lt;/span&gt;
    &lt;span class="ow"&gt;ld&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;hl&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="nb"&gt;b&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;p&gt;The above approach required that I hardcode the 8-pixel offset between the left and right halves.  With the antenna in the mix, I would&amp;#8217;ve had to hardcode another more convoluted offset, and I didn&amp;#8217;t like the sound of that.  So I changed it to &lt;code&gt;inc&lt;/code&gt; and &lt;code&gt;dec&lt;/code&gt; the &lt;span class="caps"&gt;OAM&lt;/span&gt; coordinates directly and&amp;nbsp;immediately:&lt;/p&gt;
&lt;table class="highlighttable"&gt;&lt;tr&gt;&lt;td class="linenos"&gt;&lt;div class="linenodiv"&gt;&lt;pre&gt;&lt;span class="normal"&gt; 1&lt;/span&gt;
&lt;span class="normal"&gt; 2&lt;/span&gt;
&lt;span class="normal"&gt; 3&lt;/span&gt;
&lt;span class="normal"&gt; 4&lt;/span&gt;
&lt;span class="normal"&gt; 5&lt;/span&gt;
&lt;span class="normal"&gt; 6&lt;/span&gt;
&lt;span class="normal"&gt; 7&lt;/span&gt;
&lt;span class="normal"&gt; 8&lt;/span&gt;
&lt;span class="normal"&gt; 9&lt;/span&gt;
&lt;span class="normal"&gt;10&lt;/span&gt;
&lt;span class="normal"&gt;11&lt;/span&gt;
&lt;span class="normal"&gt;12&lt;/span&gt;
&lt;span class="normal"&gt;13&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/td&gt;&lt;td class="code"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;    &lt;span class="c1"&gt;; Anise update loop&lt;/span&gt;
    &lt;span class="c1"&gt;; set b/c to the y/x coordinates&lt;/span&gt;
    &lt;span class="ow"&gt;ld&lt;/span&gt; &lt;span class="nb"&gt;bc&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;4&lt;/span&gt;
    &lt;span class="ow"&gt;bit&lt;/span&gt; &lt;span class="nv"&gt;BUTTON_LEFT&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;a&lt;/span&gt;
    &lt;span class="ow"&gt;jr&lt;/span&gt; &lt;span class="nv"&gt;z&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="err"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;skip_left&lt;/span&gt;
    &lt;span class="ow"&gt;ld&lt;/span&gt; &lt;span class="nb"&gt;hl&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;oam_buffer&lt;/span&gt; &lt;span class="p"&gt;+&lt;/span&gt; &lt;span class="nv"&gt;1&lt;/span&gt;
    &lt;span class="ow"&gt;dec&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;hl&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="ow"&gt;add&lt;/span&gt; &lt;span class="nb"&gt;hl&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;bc&lt;/span&gt;
    &lt;span class="ow"&gt;dec&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;hl&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="ow"&gt;add&lt;/span&gt; &lt;span class="nb"&gt;hl&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;bc&lt;/span&gt;
    &lt;span class="ow"&gt;dec&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;hl&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="nf"&gt;.skip_left:&lt;/span&gt;
    &lt;span class="c1"&gt;; ... etc ...&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;p&gt;Eventually I should stop doing this and have an actual canonical x/y position for Anise somewhere.  But I didn&amp;#8217;t do that&amp;nbsp;yet.&lt;/p&gt;
&lt;p&gt;I did also take this opportunity to change my &lt;span class="caps"&gt;LCDC&lt;/span&gt; flags so that object chars start counting from zero at $9000, fixing the misunderstanding I had before.  That&amp;#8217;s&amp;nbsp;nice.&lt;/p&gt;
&lt;p&gt;Anyway, tada, Star Anise can slide around, but now with his&amp;nbsp;antenna.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Not good&amp;nbsp;enough.&lt;/em&gt;&lt;/p&gt;
&lt;h2 id="animating"&gt;&lt;a class="toclink" href="#animating"&gt;Animating&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;It&amp;#8217;s time to &lt;em&gt;animate something&lt;/em&gt;.  And this time around, all I&amp;#8217;ve got are &lt;em&gt;bytes&lt;/em&gt; to work with.  Oh,&amp;nbsp;boy!&lt;/p&gt;
&lt;p&gt;Right out of the gate, I have two options.  I could load &lt;em&gt;all&lt;/em&gt; of Anise&amp;#8217;s sprites into &lt;span class="caps"&gt;VRAM&lt;/span&gt; upfront and change the char numbers in &lt;span class="caps"&gt;OAM&lt;/span&gt; to animate him, &lt;em&gt;or&lt;/em&gt; I could reserve some specific chars and overwrite them to animate&amp;nbsp;him.&lt;/p&gt;
&lt;p&gt;The first choice makes sense for an entity that might exist multiple times at once, like enemies or…  virtually anything in the game world, really.  But there&amp;#8217;s only ever one &lt;em&gt;player&lt;/em&gt;, and he&amp;#8217;s likely to have a whole lot of spritework, which I would prefer not to have clogging up my char space for the entire duration of the game.  So while I might use the other approach for most other things, I&amp;#8217;m going to animate Anise by overwriting the actual graphics.  Every&amp;nbsp;frame.&lt;/p&gt;
&lt;p&gt;First things first.  I&amp;#8217;m going to need some &lt;em&gt;state&lt;/em&gt;, which I&amp;#8217;ve been avoiding by relying on &lt;span class="caps"&gt;OAM&lt;/span&gt;.  At the very least, I need to know which way Anise is facing — which isn&amp;#8217;t necessarily the direction he&amp;#8217;s &lt;em&gt;moving&lt;/em&gt;, because he should keep his facing when he stops.  I also need to know which animation frame he&amp;#8217;s on, and how many &lt;span class="caps"&gt;LCD&lt;/span&gt; frames are left until he should advance to the next&amp;nbsp;one.&lt;/p&gt;
&lt;p&gt;Let&amp;#8217;s refer to the time between vblanks as a &amp;#8220;tic&amp;#8221; for now, to avoid the ambiguity of a &amp;#8220;frame&amp;#8221; when talking about&amp;nbsp;animation.&lt;/p&gt;
&lt;p&gt;A good start, then, would be some&amp;nbsp;constants.&lt;/p&gt;
&lt;table class="highlighttable"&gt;&lt;tr&gt;&lt;td class="linenos"&gt;&lt;div class="linenodiv"&gt;&lt;pre&gt;&lt;span class="normal"&gt;1&lt;/span&gt;
&lt;span class="normal"&gt;2&lt;/span&gt;
&lt;span class="normal"&gt;3&lt;/span&gt;
&lt;span class="normal"&gt;4&lt;/span&gt;
&lt;span class="normal"&gt;5&lt;/span&gt;
&lt;span class="normal"&gt;6&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/td&gt;&lt;td class="code"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="nf"&gt;FACING_DOWN&lt;/span&gt;   &lt;span class="k"&gt;EQU&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;
&lt;span class="nf"&gt;FACING_UP&lt;/span&gt;     &lt;span class="k"&gt;EQU&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
&lt;span class="nf"&gt;FACING_RIGHT&lt;/span&gt;  &lt;span class="k"&gt;EQU&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;
&lt;span class="nf"&gt;FACING_LEFT&lt;/span&gt;   &lt;span class="k"&gt;EQU&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;

&lt;span class="nf"&gt;ANIMATION_LENGTH&lt;/span&gt; &lt;span class="k"&gt;EQU&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;p&gt;&lt;code&gt;ANIMATION_LENGTH&lt;/code&gt; is the length of every frame.  I don&amp;#8217;t especially want to give every frame its own distinct duration if I can avoid it; this will be complicated enough as it is.  I fiddled with the frame duration in Aseprite for a bit and landed on 83ms as a nice speed, and that&amp;#8217;s 5&amp;nbsp;tics.&lt;/p&gt;
&lt;p&gt;I also need a place for this state, so I add some more stuff to my &lt;span class="caps"&gt;RAM&lt;/span&gt;&amp;nbsp;block.&lt;/p&gt;
&lt;table class="highlighttable"&gt;&lt;tr&gt;&lt;td class="linenos"&gt;&lt;div class="linenodiv"&gt;&lt;pre&gt;&lt;span class="normal"&gt;1&lt;/span&gt;
&lt;span class="normal"&gt;2&lt;/span&gt;
&lt;span class="normal"&gt;3&lt;/span&gt;
&lt;span class="normal"&gt;4&lt;/span&gt;
&lt;span class="normal"&gt;5&lt;/span&gt;
&lt;span class="normal"&gt;6&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/td&gt;&lt;td class="code"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="nf"&gt;anise_facing:&lt;/span&gt;
    &lt;span class="k"&gt;db&lt;/span&gt;
&lt;span class="nf"&gt;anise_frame:&lt;/span&gt;
    &lt;span class="k"&gt;db&lt;/span&gt;
&lt;span class="nf"&gt;anise_frame_countdown:&lt;/span&gt;
    &lt;span class="k"&gt;db&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;p&gt;And initialize it in&amp;nbsp;setup.&lt;/p&gt;
&lt;table class="highlighttable"&gt;&lt;tr&gt;&lt;td class="linenos"&gt;&lt;div class="linenodiv"&gt;&lt;pre&gt;&lt;span class="normal"&gt;1&lt;/span&gt;
&lt;span class="normal"&gt;2&lt;/span&gt;
&lt;span class="normal"&gt;3&lt;/span&gt;
&lt;span class="normal"&gt;4&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/td&gt;&lt;td class="code"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;    &lt;span class="ow"&gt;ld&lt;/span&gt; &lt;span class="nb"&gt;a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;FACING_DOWN&lt;/span&gt;
    &lt;span class="ow"&gt;ld&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;anise_facing&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="nb"&gt;a&lt;/span&gt;
    &lt;span class="ow"&gt;ld&lt;/span&gt; &lt;span class="nb"&gt;a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;ANIMATION_LENGTH&lt;/span&gt;
    &lt;span class="ow"&gt;ld&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;anise_frame_countdown&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="nb"&gt;a&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;p&gt;Presumably, one day, I&amp;#8217;ll have multiple entities, and they&amp;#8217;ll all share a similar structure, which I&amp;#8217;ll have to traverse manually.  For now, it&amp;#8217;s easier to follow the code if I give every field its own&amp;nbsp;label.&lt;/p&gt;
&lt;p&gt;I have four levels of hierarchy here: the &lt;em&gt;spriteset&lt;/em&gt; (which for now is always Anise&amp;#8217;s), the &lt;em&gt;pose&lt;/em&gt; (I only have one: walking), the &lt;em&gt;facing&lt;/em&gt;, and the &lt;em&gt;frame&lt;/em&gt;.  I need to traverse all four, but luckily I can ignore the first two for&amp;nbsp;now.&lt;/p&gt;
&lt;p&gt;I &lt;em&gt;don&amp;#8217;t&lt;/em&gt; want to animate Anise when he&amp;#8217;s not moving, so I changed the &lt;span class="caps"&gt;OAM&lt;/span&gt; updating code to also &lt;code&gt;ld d, 1&lt;/code&gt; if there&amp;#8217;s any movement at all, and skip over all the animation stuff if &lt;code&gt;d&lt;/code&gt; is still&amp;nbsp;zero.&lt;/p&gt;
&lt;table class="highlighttable"&gt;&lt;tr&gt;&lt;td class="linenos"&gt;&lt;div class="linenodiv"&gt;&lt;pre&gt;&lt;span class="normal"&gt; 1&lt;/span&gt;
&lt;span class="normal"&gt; 2&lt;/span&gt;
&lt;span class="normal"&gt; 3&lt;/span&gt;
&lt;span class="normal"&gt; 4&lt;/span&gt;
&lt;span class="normal"&gt; 5&lt;/span&gt;
&lt;span class="normal"&gt; 6&lt;/span&gt;
&lt;span class="normal"&gt; 7&lt;/span&gt;
&lt;span class="normal"&gt; 8&lt;/span&gt;
&lt;span class="normal"&gt; 9&lt;/span&gt;
&lt;span class="normal"&gt;10&lt;/span&gt;
&lt;span class="normal"&gt;11&lt;/span&gt;
&lt;span class="normal"&gt;12&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/td&gt;&lt;td class="code"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;    &lt;span class="c1"&gt;; ... read input ...&lt;/span&gt;

    &lt;span class="c1"&gt;; This was before I knew the &amp;#39;or a&amp;#39; trick; these two ops&lt;/span&gt;
    &lt;span class="c1"&gt;; could be replaced with &amp;#39;xor a; or d&amp;#39;&lt;/span&gt;
    &lt;span class="ow"&gt;ld&lt;/span&gt; &lt;span class="nb"&gt;a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;d&lt;/span&gt;
    &lt;span class="ow"&gt;cp&lt;/span&gt; &lt;span class="nb"&gt;a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;0&lt;/span&gt;
    &lt;span class="ow"&gt;jp&lt;/span&gt; &lt;span class="nv"&gt;z&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="err"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;no_movement&lt;/span&gt;

    &lt;span class="c1"&gt;; ... all the animation code will go here ...&lt;/span&gt;

&lt;span class="nf"&gt;.no_movement:&lt;/span&gt;
    &lt;span class="c1"&gt;; and after this we repeat the main loop&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;p&gt;This does have the side effect that Anise will simply freeze in mid-walk when stopped, rather than returning to his standing pose.  I still haven&amp;#8217;t fixed that; I could special-case it, but I usually treat &amp;#8220;standing&amp;#8221; as its own one-frame animation, so it feels like something that ought to come when I implement&amp;nbsp;poses.&lt;/p&gt;
&lt;p&gt;Next I decrement the countdown, which is the number of tics left until the frame ought to change.  If this is nonzero, I don&amp;#8217;t need to do&amp;nbsp;anything.&lt;/p&gt;
&lt;table class="highlighttable"&gt;&lt;tr&gt;&lt;td class="linenos"&gt;&lt;div class="linenodiv"&gt;&lt;pre&gt;&lt;span class="normal"&gt;1&lt;/span&gt;
&lt;span class="normal"&gt;2&lt;/span&gt;
&lt;span class="normal"&gt;3&lt;/span&gt;
&lt;span class="normal"&gt;4&lt;/span&gt;
&lt;span class="normal"&gt;5&lt;/span&gt;
&lt;span class="normal"&gt;6&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/td&gt;&lt;td class="code"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;    &lt;span class="ow"&gt;ld&lt;/span&gt; &lt;span class="nb"&gt;a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;anise_frame_countdown&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="ow"&gt;dec&lt;/span&gt; &lt;span class="nb"&gt;a&lt;/span&gt;
    &lt;span class="ow"&gt;ld&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;anise_frame_countdown&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="nb"&gt;a&lt;/span&gt;
    &lt;span class="ow"&gt;jp&lt;/span&gt; &lt;span class="nv"&gt;nz&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="err"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;no_movement&lt;/span&gt;
    &lt;span class="ow"&gt;ld&lt;/span&gt; &lt;span class="nb"&gt;a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;ANIMATION_LENGTH&lt;/span&gt;
    &lt;span class="ow"&gt;ld&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;anise_frame_countdown&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="nb"&gt;a&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;p&gt;Again, this isn&amp;#8217;t actually right.  If Anise&amp;#8217;s &lt;em&gt;state&lt;/em&gt; changes, such as between standing and walking, then this should be ignored because he&amp;#8217;s switching to a new animation.  But this is a pose thing again, so I&amp;#8217;m deferring it until&amp;nbsp;later.&lt;/p&gt;
&lt;p&gt;Next I need to advance the current frame.  I don&amp;#8217;t have modulo on hand and even simple ifs are kind of annoying, so I was naughty here and used bitops to roll from frame 3 to frame 0.  This would obviously not work if the number of frames were not a power of&amp;nbsp;two.&lt;/p&gt;
&lt;table class="highlighttable"&gt;&lt;tr&gt;&lt;td class="linenos"&gt;&lt;div class="linenodiv"&gt;&lt;pre&gt;&lt;span class="normal"&gt;1&lt;/span&gt;
&lt;span class="normal"&gt;2&lt;/span&gt;
&lt;span class="normal"&gt;3&lt;/span&gt;
&lt;span class="normal"&gt;4&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/td&gt;&lt;td class="code"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;    &lt;span class="ow"&gt;ld&lt;/span&gt; &lt;span class="nb"&gt;a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;anise_frame&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="ow"&gt;inc&lt;/span&gt; &lt;span class="nb"&gt;a&lt;/span&gt;
    &lt;span class="ow"&gt;and&lt;/span&gt; &lt;span class="nb"&gt;a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;4&lt;/span&gt; &lt;span class="p"&gt;-&lt;/span&gt; &lt;span class="nv"&gt;1&lt;/span&gt;
    &lt;span class="ow"&gt;ld&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;anise_frame&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="nb"&gt;a&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;p&gt;Yet again, if Anise changes &lt;em&gt;direction&lt;/em&gt;, the frame should be reset to zero…  but it&amp;nbsp;ain&amp;#8217;t.&lt;/p&gt;
&lt;p&gt;Now, let&amp;#8217;s think for a second.  I know what frame I want.  I have a label for the upper-left corner of the spritesheet, and I want to get to the upper-left corner of the appropriate frame.  Each frame has 3 objects; each object has 2 chars; each char is 16&amp;nbsp;bytes.&lt;/p&gt;
&lt;table class="highlighttable"&gt;&lt;tr&gt;&lt;td class="linenos"&gt;&lt;div class="linenodiv"&gt;&lt;pre&gt;&lt;span class="normal"&gt; 1&lt;/span&gt;
&lt;span class="normal"&gt; 2&lt;/span&gt;
&lt;span class="normal"&gt; 3&lt;/span&gt;
&lt;span class="normal"&gt; 4&lt;/span&gt;
&lt;span class="normal"&gt; 5&lt;/span&gt;
&lt;span class="normal"&gt; 6&lt;/span&gt;
&lt;span class="normal"&gt; 7&lt;/span&gt;
&lt;span class="normal"&gt; 8&lt;/span&gt;
&lt;span class="normal"&gt; 9&lt;/span&gt;
&lt;span class="normal"&gt;10&lt;/span&gt;
&lt;span class="normal"&gt;11&lt;/span&gt;
&lt;span class="normal"&gt;12&lt;/span&gt;
&lt;span class="normal"&gt;13&lt;/span&gt;
&lt;span class="normal"&gt;14&lt;/span&gt;
&lt;span class="normal"&gt;15&lt;/span&gt;
&lt;span class="normal"&gt;16&lt;/span&gt;
&lt;span class="normal"&gt;17&lt;/span&gt;
&lt;span class="normal"&gt;18&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/td&gt;&lt;td class="code"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;    &lt;span class="ow"&gt;ld&lt;/span&gt; &lt;span class="nb"&gt;hl&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;ANISE_TEST_TILES&lt;/span&gt;
    &lt;span class="c1"&gt;; Skip ahead 3 sprites * the current frame&lt;/span&gt;
    &lt;span class="ow"&gt;ld&lt;/span&gt; &lt;span class="nb"&gt;bc&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;3&lt;/span&gt; &lt;span class="err"&gt;*&lt;/span&gt; &lt;span class="nv"&gt;2&lt;/span&gt; &lt;span class="err"&gt;*&lt;/span&gt; &lt;span class="nv"&gt;16&lt;/span&gt;
    &lt;span class="c1"&gt;; Remember, zero iterations is also possible&lt;/span&gt;
    &lt;span class="ow"&gt;or&lt;/span&gt; &lt;span class="nb"&gt;a&lt;/span&gt;
    &lt;span class="ow"&gt;jr&lt;/span&gt; &lt;span class="nv"&gt;z&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="err"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;skip_advancing_frame&lt;/span&gt;
&lt;span class="nf"&gt;.advance_frame:&lt;/span&gt;    
    &lt;span class="ow"&gt;add&lt;/span&gt; &lt;span class="nb"&gt;hl&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;bc&lt;/span&gt;
    &lt;span class="ow"&gt;dec&lt;/span&gt; &lt;span class="nb"&gt;a&lt;/span&gt;
    &lt;span class="ow"&gt;jr&lt;/span&gt; &lt;span class="nv"&gt;nz&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="err"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;advance_frame&lt;/span&gt;
&lt;span class="nf"&gt;.skip_advancing_frame:&lt;/span&gt;
    &lt;span class="c1"&gt;; Copy the sprites into VRAM&lt;/span&gt;
    &lt;span class="c1"&gt;; They&amp;#39;re consecutive in both the data and VRAM, so only&lt;/span&gt;
    &lt;span class="c1"&gt;; one copy is necessary.  And bc is already right!&lt;/span&gt;
    &lt;span class="ow"&gt;ld&lt;/span&gt; &lt;span class="nb"&gt;d&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;h&lt;/span&gt;
    &lt;span class="ow"&gt;ld&lt;/span&gt; &lt;span class="nb"&gt;e&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;l&lt;/span&gt;
    &lt;span class="ow"&gt;ld&lt;/span&gt; &lt;span class="nb"&gt;hl&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mh"&gt;$8000&lt;/span&gt;
    &lt;span class="ow"&gt;call&lt;/span&gt; &lt;span class="nv"&gt;copy16&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;p&gt;Hey, look at&amp;nbsp;that!&lt;/p&gt;
&lt;div class="prose-full-illustration"&gt;
&lt;img src="https://eev.ee/media/cheezball/04k-go-anise-go.gif" alt="Star Anise walking around in-game, now animated"&gt;
&lt;/div&gt;

&lt;p&gt;Only one small problem: I forgot about facing, so Anise will always face forwards no matter how he moves.&amp;nbsp;Whoops!&lt;/p&gt;
&lt;h2 id="facing"&gt;&lt;a class="toclink" href="#facing"&gt;Facing&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;I need to actually &lt;em&gt;track&lt;/em&gt; which way Anise is facing, which is a surprisingly subtle question.  He might even be facing &lt;em&gt;away from&lt;/em&gt; his own direction of movement, if for example he was thrown backwards by some external&amp;nbsp;force.&lt;/p&gt;
&lt;p&gt;A decent first approximation is to use the last button that was pressed.  (That&amp;#8217;s still not quite right — if you hold down, hold down+right, and then release right, he should obviously face down.  But it&amp;#8217;s a&amp;nbsp;start.)&lt;/p&gt;
&lt;p&gt;I don&amp;#8217;t yet track which buttons were pressed this frame, but it&amp;#8217;s easy enough to add.  While I&amp;#8217;m at it, I might as well track which buttons were released, too.  I amend the input reading code thusly, based on the straightforward insight that a button was pressed this frame iff it is currently 1 and was previously&amp;nbsp;0.&lt;/p&gt;
&lt;table class="highlighttable"&gt;&lt;tr&gt;&lt;td class="linenos"&gt;&lt;div class="linenodiv"&gt;&lt;pre&gt;&lt;span class="normal"&gt; 1&lt;/span&gt;
&lt;span class="normal"&gt; 2&lt;/span&gt;
&lt;span class="normal"&gt; 3&lt;/span&gt;
&lt;span class="normal"&gt; 4&lt;/span&gt;
&lt;span class="normal"&gt; 5&lt;/span&gt;
&lt;span class="normal"&gt; 6&lt;/span&gt;
&lt;span class="normal"&gt; 7&lt;/span&gt;
&lt;span class="normal"&gt; 8&lt;/span&gt;
&lt;span class="normal"&gt; 9&lt;/span&gt;
&lt;span class="normal"&gt;10&lt;/span&gt;
&lt;span class="normal"&gt;11&lt;/span&gt;
&lt;span class="normal"&gt;12&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/td&gt;&lt;td class="code"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;    &lt;span class="c1"&gt;; a now contains the current buttons&lt;/span&gt;
    &lt;span class="ow"&gt;ld&lt;/span&gt; &lt;span class="nb"&gt;hl&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;buttons&lt;/span&gt;
    &lt;span class="ow"&gt;ld&lt;/span&gt; &lt;span class="nb"&gt;b&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;hl&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;                  &lt;span class="c1"&gt;; b &amp;lt;- previous buttons&lt;/span&gt;
    &lt;span class="ow"&gt;ld&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;hl&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="nb"&gt;a&lt;/span&gt;                  &lt;span class="c1"&gt;; a -&amp;gt; current buttons&lt;/span&gt;
    &lt;span class="ow"&gt;cpl&lt;/span&gt;
    &lt;span class="ow"&gt;and&lt;/span&gt; &lt;span class="nb"&gt;a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;b&lt;/span&gt;
    &lt;span class="ow"&gt;ld&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;buttons_released&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="nb"&gt;a&lt;/span&gt;    &lt;span class="c1"&gt;; a = ~new &amp;amp; old, i.e. released&lt;/span&gt;
    &lt;span class="ow"&gt;ld&lt;/span&gt; &lt;span class="nb"&gt;a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;hl&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;                  &lt;span class="c1"&gt;; a &amp;lt;- current buttons&lt;/span&gt;
    &lt;span class="ow"&gt;cpl&lt;/span&gt;
    &lt;span class="ow"&gt;or&lt;/span&gt; &lt;span class="nb"&gt;a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;b&lt;/span&gt;
    &lt;span class="ow"&gt;cpl&lt;/span&gt;
    &lt;span class="ow"&gt;ld&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;buttons_pressed&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="nb"&gt;a&lt;/span&gt;     &lt;span class="c1"&gt;; a = ~(~new | old), i.e. pressed&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;p&gt;I like that cute trick for getting the pressed buttons.  I need &lt;code&gt;a &amp;amp; ~b&lt;/code&gt;, but &lt;code&gt;cpl&lt;/code&gt; only works on &lt;code&gt;a&lt;/code&gt;, so I would&amp;#8217;ve had to juggle a bunch of registers.  But applying De Morgan&amp;#8217;s law produces &lt;code&gt;~(~a | b)&lt;/code&gt;, which only requires complementing &lt;code&gt;a&lt;/code&gt;.  (Full disclosure: I didn&amp;#8217;t actually try register juggling, and for all I know it could end up shorter&amp;nbsp;somehow.)&lt;/p&gt;
&lt;p&gt;Next I check the just-pressed buttons and updating facing accordingly.  It looks a lot like the code for checking the currently-held buttons, except that I only use the first button I&amp;nbsp;find.&lt;/p&gt;
&lt;table class="highlighttable"&gt;&lt;tr&gt;&lt;td class="linenos"&gt;&lt;div class="linenodiv"&gt;&lt;pre&gt;&lt;span class="normal"&gt;1&lt;/span&gt;
&lt;span class="normal"&gt;2&lt;/span&gt;
&lt;span class="normal"&gt;3&lt;/span&gt;
&lt;span class="normal"&gt;4&lt;/span&gt;
&lt;span class="normal"&gt;5&lt;/span&gt;
&lt;span class="normal"&gt;6&lt;/span&gt;
&lt;span class="normal"&gt;7&lt;/span&gt;
&lt;span class="normal"&gt;8&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/td&gt;&lt;td class="code"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;    &lt;span class="ow"&gt;ld&lt;/span&gt; &lt;span class="nb"&gt;hl&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;anise_facing&lt;/span&gt;
    &lt;span class="ow"&gt;ld&lt;/span&gt; &lt;span class="nb"&gt;a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;buttons_pressed&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="ow"&gt;bit&lt;/span&gt; &lt;span class="nv"&gt;BUTTON_LEFT&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;a&lt;/span&gt;
    &lt;span class="ow"&gt;jr&lt;/span&gt; &lt;span class="nv"&gt;z&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="err"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;skip_left2&lt;/span&gt;
    &lt;span class="ow"&gt;ld&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;hl&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="nv"&gt;FACING_LEFT&lt;/span&gt;
    &lt;span class="ow"&gt;jr&lt;/span&gt; &lt;span class="err"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;skip_down2&lt;/span&gt;
&lt;span class="nf"&gt;.skip_left2:&lt;/span&gt;
    &lt;span class="c1"&gt;; ... you get the idea ...&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;p&gt;And finally, amend the sprite choosing code to pick the right facing,&amp;nbsp;too.&lt;/p&gt;
&lt;table class="highlighttable"&gt;&lt;tr&gt;&lt;td class="linenos"&gt;&lt;div class="linenodiv"&gt;&lt;pre&gt;&lt;span class="normal"&gt; 1&lt;/span&gt;
&lt;span class="normal"&gt; 2&lt;/span&gt;
&lt;span class="normal"&gt; 3&lt;/span&gt;
&lt;span class="normal"&gt; 4&lt;/span&gt;
&lt;span class="normal"&gt; 5&lt;/span&gt;
&lt;span class="normal"&gt; 6&lt;/span&gt;
&lt;span class="normal"&gt; 7&lt;/span&gt;
&lt;span class="normal"&gt; 8&lt;/span&gt;
&lt;span class="normal"&gt; 9&lt;/span&gt;
&lt;span class="normal"&gt;10&lt;/span&gt;
&lt;span class="normal"&gt;11&lt;/span&gt;
&lt;span class="normal"&gt;12&lt;/span&gt;
&lt;span class="normal"&gt;13&lt;/span&gt;
&lt;span class="normal"&gt;14&lt;/span&gt;
&lt;span class="normal"&gt;15&lt;/span&gt;
&lt;span class="normal"&gt;16&lt;/span&gt;
&lt;span class="normal"&gt;17&lt;/span&gt;
&lt;span class="normal"&gt;18&lt;/span&gt;
&lt;span class="normal"&gt;19&lt;/span&gt;
&lt;span class="normal"&gt;20&lt;/span&gt;
&lt;span class="normal"&gt;21&lt;/span&gt;
&lt;span class="normal"&gt;22&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/td&gt;&lt;td class="code"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;    &lt;span class="ow"&gt;ld&lt;/span&gt; &lt;span class="nb"&gt;hl&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;ANISE_TEST_TILES&lt;/span&gt;

    &lt;span class="c1"&gt;; Skip ahead a number of /rows/, corresponding to facing&lt;/span&gt;
    &lt;span class="ow"&gt;ld&lt;/span&gt; &lt;span class="nb"&gt;a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;anise_facing&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="ow"&gt;and&lt;/span&gt; &lt;span class="nb"&gt;a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mb"&gt;%11&lt;/span&gt;                      &lt;span class="c1"&gt;; cap to 4, just in case&lt;/span&gt;
    &lt;span class="ow"&gt;jr&lt;/span&gt; &lt;span class="nv"&gt;z&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="err"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;skip_stride_row&lt;/span&gt;
    &lt;span class="c1"&gt;; This is like before, but times 4 frames&lt;/span&gt;
    &lt;span class="ow"&gt;ld&lt;/span&gt; &lt;span class="nb"&gt;bc&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;4&lt;/span&gt; &lt;span class="err"&gt;*&lt;/span&gt; &lt;span class="nv"&gt;3&lt;/span&gt; &lt;span class="err"&gt;*&lt;/span&gt; &lt;span class="nv"&gt;2&lt;/span&gt; &lt;span class="err"&gt;*&lt;/span&gt; &lt;span class="nv"&gt;16&lt;/span&gt;
&lt;span class="nf"&gt;.stride_row:&lt;/span&gt;
    &lt;span class="ow"&gt;add&lt;/span&gt; &lt;span class="nb"&gt;hl&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;bc&lt;/span&gt;
    &lt;span class="ow"&gt;dec&lt;/span&gt; &lt;span class="nb"&gt;a&lt;/span&gt;
    &lt;span class="ow"&gt;jr&lt;/span&gt; &lt;span class="nv"&gt;nz&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="err"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;stride_row&lt;/span&gt;
&lt;span class="nf"&gt;.skip_stride_row:&lt;/span&gt;

    &lt;span class="c1"&gt;; Bumping the frame here is convenient, since it leaves the&lt;/span&gt;
    &lt;span class="c1"&gt;; frame in a for the next part&lt;/span&gt;
    &lt;span class="ow"&gt;ld&lt;/span&gt; &lt;span class="nb"&gt;a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;anise_frame&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="ow"&gt;inc&lt;/span&gt; &lt;span class="nb"&gt;a&lt;/span&gt;
    &lt;span class="ow"&gt;and&lt;/span&gt; &lt;span class="nb"&gt;a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;4&lt;/span&gt; &lt;span class="p"&gt;-&lt;/span&gt; &lt;span class="nv"&gt;1&lt;/span&gt;
    &lt;span class="ow"&gt;ld&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;anise_frame&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="nb"&gt;a&lt;/span&gt;

    &lt;span class="c1"&gt;; ... continue on with picking the frame ...&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;p&gt;Hardcoding the number of frames here is…  unfortunate.  I should probably flip the spritesheet so the frames go &lt;em&gt;down&lt;/em&gt; and each column is a facing; then there&amp;#8217;ll always be a fixed number of columns to skip&amp;nbsp;over.&lt;/p&gt;
&lt;p&gt;But who cares about that?  Look at Anise go!&amp;nbsp;Yeah!&lt;/p&gt;
&lt;div class="prose-full-illustration"&gt;
&lt;img src="https://eev.ee/media/cheezball/04l-go-anise-go-again.gif" alt="Star Anise walking around in-game, now animated in all four directions"&gt;
&lt;/div&gt;

&lt;p&gt;Well, yes, there is &lt;em&gt;one&lt;/em&gt; final problem, which is that the antenna is misaligned when walking left or right…  because its positioning is different than when walking up or down, and I don&amp;#8217;t have any easy way to encode that at the moment.  It&amp;#8217;s &lt;em&gt;still&lt;/em&gt; like that, in fact.  I&amp;#8217;m sure I&amp;#8217;ll fix it&amp;nbsp;eventually.&lt;/p&gt;
&lt;h2 id="more-vblank-woes"&gt;&lt;a class="toclink" href="#more-vblank-woes"&gt;More vblank&amp;nbsp;woes&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;I didn&amp;#8217;t run into this problem until a little while later, but I might as well mention it now.  The above code writes into &lt;span class="caps"&gt;VRAM&lt;/span&gt; in the middle of updating entities — updating them very simply, perhaps, but updating nonetheless.  If that updating takes longer than vblank, the write will&amp;nbsp;fail.&lt;/p&gt;
&lt;p&gt;I expected this, though not quite so soon.  It&amp;#8217;s a disadvantage of swapping the char data rather than the char references: 32× more writing to do, which will take 32× longer.  The solution is similar to what I do for &lt;span class="caps"&gt;OAM&lt;/span&gt;: defer the write until the next vblank.  I&amp;#8217;m already doing that with Anise&amp;#8217;s &lt;em&gt;position&lt;/em&gt;, anyway, and it makes no sense to have his position and animation updated on different&amp;nbsp;frames.&lt;/p&gt;
&lt;p&gt;I ended up special-casing this for Anise, though it wouldn&amp;#8217;t be too hard to extend this into a queue of tiles to copy.  It&amp;#8217;s nothing too world-shaking; I just store the address of Anise&amp;#8217;s current sprite in &lt;span class="caps"&gt;RAM&lt;/span&gt;, then copy it over during vblank, just after the &lt;span class="caps"&gt;OAM&lt;/span&gt; &lt;span class="caps"&gt;DMA&lt;/span&gt;.&lt;/p&gt;
&lt;p&gt;I &lt;em&gt;did&lt;/em&gt; try doing this with one of the Game Boy Color&amp;#8217;s new features, &lt;a href="http://gbdev.gg8.se/wiki/articles/Video_Display#LCD_VRAM_DMA_Transfers_.28CGB_only.29"&gt;&lt;em&gt;general-purpose&lt;/em&gt; &lt;span class="caps"&gt;DMA&lt;/span&gt;&lt;/a&gt;, which can copy from basically anywhere in &lt;span class="caps"&gt;ROM&lt;/span&gt; or &lt;span class="caps"&gt;RAM&lt;/span&gt; to basically anywhere in &lt;span class="caps"&gt;VRAM&lt;/span&gt;.  It involves five registers: you write the source address in the first two, the destination in the next two, and the length in the fifth, which triggers the copy.  The &lt;span class="caps"&gt;CPU&lt;/span&gt; simply freezes until the copy is done, so there are no goofy timing issues&amp;nbsp;here.&lt;/p&gt;
&lt;table class="highlighttable"&gt;&lt;tr&gt;&lt;td class="linenos"&gt;&lt;div class="linenodiv"&gt;&lt;pre&gt;&lt;span class="normal"&gt; 1&lt;/span&gt;
&lt;span class="normal"&gt; 2&lt;/span&gt;
&lt;span class="normal"&gt; 3&lt;/span&gt;
&lt;span class="normal"&gt; 4&lt;/span&gt;
&lt;span class="normal"&gt; 5&lt;/span&gt;
&lt;span class="normal"&gt; 6&lt;/span&gt;
&lt;span class="normal"&gt; 7&lt;/span&gt;
&lt;span class="normal"&gt; 8&lt;/span&gt;
&lt;span class="normal"&gt; 9&lt;/span&gt;
&lt;span class="normal"&gt;10&lt;/span&gt;
&lt;span class="normal"&gt;11&lt;/span&gt;
&lt;span class="normal"&gt;12&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/td&gt;&lt;td class="code"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;    &lt;span class="ow"&gt;ld&lt;/span&gt; &lt;span class="nb"&gt;hl&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;anise_sprites_address&lt;/span&gt;
    &lt;span class="ow"&gt;ld&lt;/span&gt; &lt;span class="nb"&gt;a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;hl&lt;/span&gt;&lt;span class="p"&gt;+]&lt;/span&gt;
    &lt;span class="ow"&gt;ld&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;rHDMA1&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="nb"&gt;a&lt;/span&gt;
    &lt;span class="ow"&gt;ld&lt;/span&gt; &lt;span class="nb"&gt;a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;hl&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="ow"&gt;ld&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;rHDMA2&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="nb"&gt;a&lt;/span&gt;
    &lt;span class="ow"&gt;ld&lt;/span&gt; &lt;span class="nb"&gt;a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;HIGH&lt;/span&gt;&lt;span class="err"&gt;(&lt;/span&gt;&lt;span class="mh"&gt;$0000&lt;/span&gt;&lt;span class="err"&gt;)&lt;/span&gt;
    &lt;span class="ow"&gt;ld&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;rHDMA3&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="nb"&gt;a&lt;/span&gt;
    &lt;span class="ow"&gt;ld&lt;/span&gt; &lt;span class="nb"&gt;a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;LOW&lt;/span&gt;&lt;span class="err"&gt;(&lt;/span&gt;&lt;span class="mh"&gt;$0000&lt;/span&gt;&lt;span class="err"&gt;)&lt;/span&gt;
    &lt;span class="ow"&gt;ld&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;rHDMA4&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="nb"&gt;a&lt;/span&gt;
    &lt;span class="c1"&gt;; To copy X bytes, write X / 16 - 1 to this register&lt;/span&gt;
    &lt;span class="ow"&gt;ld&lt;/span&gt; &lt;span class="nb"&gt;a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="err"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;32&lt;/span&gt; &lt;span class="err"&gt;*&lt;/span&gt; &lt;span class="nv"&gt;3&lt;/span&gt;&lt;span class="err"&gt;)&lt;/span&gt; &lt;span class="err"&gt;/&lt;/span&gt; &lt;span class="nv"&gt;16&lt;/span&gt; &lt;span class="p"&gt;-&lt;/span&gt; &lt;span class="nv"&gt;1&lt;/span&gt;
    &lt;span class="ow"&gt;ld&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;rHDMA5&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="nb"&gt;a&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;p&gt;General-purpose &lt;span class="caps"&gt;DMA&lt;/span&gt; can copy 16 bytes every 8 cycles, or ½ cycle per byte.  The fastest possible manual copy would be an unrolled series of &lt;code&gt;ld a, [hl+]; ld [bc], a; inc bc&lt;/code&gt; which takes a whopping 6 cycles per byte — twelve times slower!  This is a neat&amp;nbsp;feature.&lt;/p&gt;
&lt;p&gt;The only gotcha is that the source address has to be 16-byte aligned, so I need the &lt;code&gt;SECTION&lt;/code&gt; header to look&amp;nbsp;like:&lt;/p&gt;
&lt;table class="highlighttable"&gt;&lt;tr&gt;&lt;td class="linenos"&gt;&lt;div class="linenodiv"&gt;&lt;pre&gt;&lt;span class="normal"&gt;1&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/td&gt;&lt;td class="code"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="k"&gt;SECTION &amp;quot;Anise sprites&amp;quot;, ROM0, ALIGN[4]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;p&gt;Each character is 16 bytes, so now I can copy starting from any one of&amp;nbsp;them.&lt;/p&gt;
&lt;p&gt;I edited my local copy of the built file to have this annotation, but of course, the script doesn&amp;#8217;t know about it yet.  Whoops, again!  I should really fix the build, &lt;em&gt;ahem&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;&lt;span class="caps"&gt;FYI&lt;/span&gt;, it&amp;#8217;s also possible to have a copy done piecemeal during hblanks, though that sounds a bit fragile to&amp;nbsp;me.&lt;/p&gt;
&lt;h2 id="future-work"&gt;&lt;a class="toclink" href="#future-work"&gt;Future&amp;nbsp;work&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;I&amp;#8217;ve laid some very basic groundwork here, and there&amp;#8217;s plenty more to do, which I will get back to later!  It&amp;#8217;s just me hacking all this together, after all, and I like flitting between different&amp;nbsp;systems.&lt;/p&gt;
&lt;p&gt;I will &lt;em&gt;definitely&lt;/em&gt; need to figure out how the heck multiple tilesets work and when they get switched out.  How do I even &lt;em&gt;use&lt;/em&gt; multiple tilesets, each with its own set of palettes?  What&amp;#8217;s the workflow if I want to use the same tiles with several different palettes, like how the graveyard in Oracle of Ages is tinted purple?  And I didn&amp;#8217;t even implement character de-duplication yet…  which will require some metadata for each tile…  aw,&amp;nbsp;geez.&lt;/p&gt;
&lt;p&gt;And I still haven&amp;#8217;t fixed the build system!  Maybe you can understand why I&amp;#8217;m hesitant to impose more structure on this idea quite&amp;nbsp;yet.&lt;/p&gt;
&lt;h2 id="to-be-continued"&gt;&lt;a class="toclink" href="#to-be-continued"&gt;To be&amp;nbsp;continued&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;That brings us to &lt;a href="https://github.com/eevee/anise-cheezball-rising/commit/59ff18f391ac6f9627c6b6b863796caf3d4df16d"&gt;commit 59ff18&lt;/a&gt;.  Except for a commit about the build that I skipped.  Whatever.  This post has been a little more draining to write, perhaps because it forced me to confront and explain a bunch of hokey&amp;nbsp;decisions.&lt;/p&gt;
&lt;p&gt;Next time: &lt;a href="https://eev.ee/blog/2018/09/06/cheezball-rising-resounding-failure/"&gt;&lt;em&gt;resounding &lt;s&gt;failure&lt;/s&gt; success&lt;/em&gt;&lt;/a&gt;!&lt;/p&gt;</content><category term="blog"></category><category term="tech"></category><category term="gamedev"></category><category term="cheezball rising"></category></entry><entry><title>Cheezball Rising: Spring cleaning</title><link href="https://eev.ee/blog/2018/07/13/cheezball-rising-spring-cleaning/" rel="alternate"></link><published>2018-07-13T13:55:00-07:00</published><updated>2018-07-13T13:55:00-07:00</updated><author><name>Eevee</name></author><id>tag:eev.ee,2018-07-13:/blog/2018/07/13/cheezball-rising-spring-cleaning/</id><summary type="html">&lt;p&gt;This is a series about &lt;a href="https://github.com/eevee/anise-cheezball-rising"&gt;&lt;strong&gt;Star Anise Chronicles: Cheezball Rising&lt;/strong&gt;&lt;/a&gt;, an expansive adventure game about my cat for the Game Boy Color.  Follow along as I struggle to make something with this bleeding-edge console!&lt;/p&gt;
&lt;p&gt;GitHub has &lt;a href="https://github.com/eevee/anise-cheezball-rising/releases"&gt;intermittent prebuilt ROMs&lt;/a&gt;, or you can get them a week early &lt;a href="https://www.patreon.com/eevee/posts?tag=cheezball%20rising"&gt;on Patreon&lt;/a&gt; if you pledge $4.  More details in the &lt;a href="https://github.com/eevee/anise-cheezball-rising"&gt;&lt;span class="caps"&gt;README&lt;/span&gt;&lt;/a&gt;!&lt;/p&gt;
&lt;hr/&gt;
&lt;p&gt;In this issue, I tidy up some of the gigantic mess I’ve made thusfar.&lt;/p&gt;
&lt;p&gt;Previously: &lt;a href="https://eev.ee/blog/2018/07/05/cheezball-rising-main-loop-input-and-a-game/"&gt;writing a main loop, and finally getting something game-like&lt;/a&gt;.&lt;br/&gt;
Next: &lt;a href="https://eev.ee/blog/2018/07/15/cheezball-rising-maps-and-sprites/"&gt;sprite and map loading&lt;/a&gt;.&lt;/p&gt;
</summary><content type="html">&lt;p&gt;This is a series about &lt;a href="https://github.com/eevee/anise-cheezball-rising"&gt;&lt;strong&gt;Star Anise Chronicles: Cheezball Rising&lt;/strong&gt;&lt;/a&gt;, an expansive adventure game about my cat for the Game Boy Color.  Follow along as I struggle to make something with this bleeding-edge&amp;nbsp;console!&lt;/p&gt;
&lt;p&gt;GitHub has &lt;a href="https://github.com/eevee/anise-cheezball-rising/releases"&gt;intermittent prebuilt ROMs&lt;/a&gt;, or you can get them a week early &lt;a href="https://www.patreon.com/eevee/posts?tag=cheezball%20rising"&gt;on Patreon&lt;/a&gt; if you pledge $4.  More details in the &lt;a href="https://github.com/eevee/anise-cheezball-rising"&gt;&lt;span class="caps"&gt;README&lt;/span&gt;&lt;/a&gt;!&lt;/p&gt;
&lt;hr /&gt;
&lt;p&gt;In this issue, I tidy up some of the gigantic mess I&amp;#8217;ve made&amp;nbsp;thusfar.&lt;/p&gt;
&lt;p&gt;Previously: &lt;a href="https://eev.ee/blog/2018/07/05/cheezball-rising-main-loop-input-and-a-game/"&gt;writing a main loop, and finally getting something game-like&lt;/a&gt;.&lt;br /&gt;
Next: &lt;a href="https://eev.ee/blog/2018/07/15/cheezball-rising-maps-and-sprites/"&gt;sprite and map loading&lt;/a&gt;.&lt;/p&gt;


&lt;h2 id="recap"&gt;&lt;a class="toclink" href="#recap"&gt;Recap&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;After only a few long, winding posts&amp;#8217; worth of effort, I finally have a game, if you define &amp;#8220;game&amp;#8221; loosely as a thing that reacts when you press&amp;nbsp;buttons.&lt;/p&gt;
&lt;div class="prose-full-illustration"&gt;
&lt;img src="https://eev.ee/media/cheezball/02d-anise-move-grass.gif" alt="A space cat roams around on a grassy background"&gt;
&lt;/div&gt;

&lt;p&gt;Beautiful.  But to make an omelette, you need to break a few eggs, and if it&amp;#8217;s your first omelette then you might break some glassware too.  As tiny as this game is, a couple things could use&amp;nbsp;improvement.&lt;/p&gt;
&lt;p&gt;Also, for narrative purposes, it&amp;#8217;s much more interesting to put all these miscellaneous fixes together, rather than interrupting other posts with them.  I didn&amp;#8217;t actually do all this work in one lump in this order.  Apologies to the die-hard non-fiction&amp;nbsp;crowd.&lt;/p&gt;
&lt;h2 id="its-totally-broken"&gt;&lt;a class="toclink" href="#its-totally-broken"&gt;It's totally&amp;nbsp;broken&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Ah, the elephant in the room.  The end of the previous post aligned with the first &lt;a href="https://github.com/eevee/anise-cheezball-rising/releases/tag/v20180619pre"&gt;demo build&lt;/a&gt;, but if you downloaded it and tried to play it, you &lt;em&gt;may&lt;/em&gt; have seen something that looks more like&amp;nbsp;this:&lt;/p&gt;
&lt;div class="prose-full-illustration"&gt;
&lt;img src="https://eev.ee/media/cheezball/03a-broken.png" alt="Similar to the previous image, but with obvious graphical corruption"&gt;
&lt;/div&gt;

&lt;p&gt;I said &lt;a href="https://eev.ee/blog/2018/06/19/cheezball-rising-a-new-game-boy-color-game/"&gt;in the beginning&lt;/a&gt; that I liked mGBA and would be developing against it.  That&amp;#8217;s still true — it&amp;#8217;s open source (and I&amp;#8217;ve actually read some of it), it&amp;#8217;s cross-platform, and it has some debug tools built&amp;nbsp;in.&lt;/p&gt;
&lt;p&gt;I &lt;em&gt;also&lt;/em&gt; said that emulators are primarily designed to &lt;em&gt;accept correct games&lt;/em&gt;, not necessarily to reject incorrect games.  And that&amp;#8217;s still very&amp;nbsp;true.&lt;/p&gt;
&lt;p&gt;I discovered this problem myself a little later (after the events of the next post), while shopping around a bit for emulators explicitly focused on accuracy.  The one I keep being told to use is &lt;a href="http://bgb.bircd.org/"&gt;bgb&lt;/a&gt;, but it&amp;#8217;s for Windows and Wine is kind of annoying, so I was exploring my other options; I found &lt;a href="https://sameboy.github.io/"&gt;SameBoy&lt;/a&gt; (primarily for Mac, but with Linux and Windows builds sans debug features) and &lt;a href="https://github.com/sinamas/gambatte"&gt;Gambatte&lt;/a&gt; (cross-platform, and the core for RetroArch&amp;#8217;s Game Boy emulation).  All &lt;em&gt;three&lt;/em&gt; of them looked like the screenshot&amp;nbsp;above.&lt;/p&gt;
&lt;p&gt;Something was going very wrong when writing to &lt;span class="caps"&gt;VRAM&lt;/span&gt;.  You can&amp;#8217;t write to &lt;span class="caps"&gt;VRAM&lt;/span&gt; while the &lt;span class="caps"&gt;LCD&lt;/span&gt; is redrawing, so the most obvious cause is that…  well…  maybe the &lt;span class="caps"&gt;LCD&lt;/span&gt; is redrawing during my setup&amp;nbsp;code.&lt;/p&gt;
&lt;p&gt;Remember, on an &lt;em&gt;actual&lt;/em&gt; Game Boy, the system doesn&amp;#8217;t immediately start running what&amp;#8217;s on the cartridge — it scrolls in the Nintendo logo first (or on a Color, does a fancier logo with a cool fanfare).  That&amp;#8217;s done by a tiny internal program called the &lt;em&gt;boot &lt;span class="caps"&gt;ROM&lt;/span&gt;&lt;/em&gt;, and the state of the &lt;span class="caps"&gt;LCD&lt;/span&gt; when the boot &lt;span class="caps"&gt;ROM&lt;/span&gt; hands over control is undefined.  I&amp;#8217;m sure it&amp;#8217;s &lt;em&gt;consistent&lt;/em&gt;, but it&amp;#8217;s not anything in particular, and for all I know it might be when the &lt;span class="caps"&gt;LCD&lt;/span&gt; is halfway through a&amp;nbsp;redraw.&lt;/p&gt;
&lt;p&gt;(Side note: I am violating Nintendo&amp;#8217;s game submission requirements by consistently referring to it as a &amp;#8220;cartridge&amp;#8221; when in fact it is properly called a Game Pak.  My&amp;nbsp;bad.)&lt;/p&gt;
&lt;p&gt;So what we&amp;#8217;re seeing above is the result of &lt;span class="caps"&gt;VRAM&lt;/span&gt; becoming locked and unlocked as the &lt;span class="caps"&gt;LCD&lt;/span&gt; draws (remember, after every row is an hblank, during which time &lt;span class="caps"&gt;VRAM&lt;/span&gt; is accessible), &lt;em&gt;while&lt;/em&gt; I&amp;#8217;m trying to copy blocks of data there.  In fact, &lt;em&gt;every&lt;/em&gt; emulator I&amp;#8217;ve tried shows a slightly different form of corruption, since this problem is very sensitive to timing accuracy.  Super&amp;nbsp;interesting!&lt;/p&gt;
&lt;p&gt;I &lt;em&gt;could&lt;/em&gt; wait for vblank and try to squeeze in all my setup code there, maybe even split across several vblanks.  But since this is &lt;em&gt;setup&lt;/em&gt; code and doesn&amp;#8217;t run during gameplay, there&amp;#8217;s a much easier solution: &lt;em&gt;turn the screen off&lt;/em&gt;.  That&amp;#8217;s done with a bit in the &lt;span class="caps"&gt;LCDC&lt;/span&gt; register, which I currently configure at the end of my setup code; all I need to do is move that to the beginning and clear the appropriate bit&amp;nbsp;instead.&lt;/p&gt;
&lt;table class="highlighttable"&gt;&lt;tr&gt;&lt;td class="linenos"&gt;&lt;div class="linenodiv"&gt;&lt;pre&gt;&lt;span class="normal"&gt;1&lt;/span&gt;
&lt;span class="normal"&gt;2&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/td&gt;&lt;td class="code"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;    &lt;span class="ow"&gt;ld&lt;/span&gt; &lt;span class="nb"&gt;a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mb"&gt;%00010111&lt;/span&gt;  &lt;span class="c1"&gt;; $91 plus bit 2, minus bit 7&lt;/span&gt;
    &lt;span class="ow"&gt;ld&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mh"&gt;$ff40&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="nb"&gt;a&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;p&gt;Then, of course, set it again once I&amp;#8217;m done.  I did this with a couple macros, since it&amp;#8217;s only a few instructions and it seems like the kind of thing I might need again&amp;nbsp;later.&lt;/p&gt;
&lt;table class="highlighttable"&gt;&lt;tr&gt;&lt;td class="linenos"&gt;&lt;div class="linenodiv"&gt;&lt;pre&gt;&lt;span class="normal"&gt; 1&lt;/span&gt;
&lt;span class="normal"&gt; 2&lt;/span&gt;
&lt;span class="normal"&gt; 3&lt;/span&gt;
&lt;span class="normal"&gt; 4&lt;/span&gt;
&lt;span class="normal"&gt; 5&lt;/span&gt;
&lt;span class="normal"&gt; 6&lt;/span&gt;
&lt;span class="normal"&gt; 7&lt;/span&gt;
&lt;span class="normal"&gt; 8&lt;/span&gt;
&lt;span class="normal"&gt; 9&lt;/span&gt;
&lt;span class="normal"&gt;10&lt;/span&gt;
&lt;span class="normal"&gt;11&lt;/span&gt;
&lt;span class="normal"&gt;12&lt;/span&gt;
&lt;span class="normal"&gt;13&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/td&gt;&lt;td class="code"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="nf"&gt;DisableLCD:&lt;/span&gt; &lt;span class="k"&gt;MACRO&lt;/span&gt;
    &lt;span class="ow"&gt;ld&lt;/span&gt; &lt;span class="nb"&gt;a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mh"&gt;$ff40&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="ow"&gt;and&lt;/span&gt; &lt;span class="nb"&gt;a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mb"&gt;%0111111&lt;/span&gt;
    &lt;span class="ow"&gt;ld&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mh"&gt;$ff40&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="nb"&gt;a&lt;/span&gt;
&lt;span class="k"&gt;ENDM&lt;/span&gt;

&lt;span class="nf"&gt;EnableLCD:&lt;/span&gt; &lt;span class="k"&gt;MACRO&lt;/span&gt;
    &lt;span class="ow"&gt;ld&lt;/span&gt; &lt;span class="nb"&gt;a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mh"&gt;$ff40&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="ow"&gt;or&lt;/span&gt; &lt;span class="nb"&gt;a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mb"&gt;%10000000&lt;/span&gt;
    &lt;span class="ow"&gt;ld&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mh"&gt;$ff40&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="nb"&gt;a&lt;/span&gt;
&lt;span class="k"&gt;ENDM&lt;/span&gt;

&lt;span class="c1"&gt;; and, of course, stick an EnableLCD at the end of setup code&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;p&gt;Note that when the screen is off, &lt;em&gt;it&amp;#8217;s off&lt;/em&gt;, and there are no vblank interrupts or anything else that might be triggered by the screen&amp;#8217;s behavior.  So, you know, don&amp;#8217;t wait for vblank while the screen&amp;#8217;s off.  When the screen turns back on, it immediately starts redrawing from the first row, so don&amp;#8217;t try to use &lt;span class="caps"&gt;VRAM&lt;/span&gt; right away either.  Finally, on the original Game Boy, &lt;strong&gt;do not&lt;/strong&gt; turn off the screen when it&amp;#8217;s not in vblank, or &lt;strong&gt;you might physically damage the screen&lt;/strong&gt;.  It&amp;#8217;s fine on the Game Boy Color, but…  hell, I&amp;#8217;m gonna edit this to wait for vblank anyway.  Feels kinda inappropriate to abruptly turn off the screen halfway through&amp;nbsp;drawing.&lt;/p&gt;
&lt;p&gt;Anyway, that solves my goofy corruption problems, and now the game looks the same on all of these emulators!  I also reported this misbehavior, and it&amp;#8217;s since been fixed, so recent dev builds of mGBA also correctly render garbage for the first release.  See, by &lt;em&gt;not&lt;/em&gt; targeting the most accurate emulators, I&amp;#8217;ve caused another emulator to become more&amp;nbsp;accurate!&lt;/p&gt;
&lt;h2 id="hardwareinc"&gt;&lt;a class="toclink" href="#hardwareinc"&gt;hardware.inc&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;I mentioned last time that I&amp;#8217;d adopted &lt;a href="https://github.com/tobiasvl/hardware.inc"&gt;hardware.inc&lt;/a&gt;.  That&amp;#8217;s in large part because I keep producing monstrosities like the previous snippet.  Here are those macros with some symbolic&amp;nbsp;constants:&lt;/p&gt;
&lt;table class="highlighttable"&gt;&lt;tr&gt;&lt;td class="linenos"&gt;&lt;div class="linenodiv"&gt;&lt;pre&gt;&lt;span class="normal"&gt; 1&lt;/span&gt;
&lt;span class="normal"&gt; 2&lt;/span&gt;
&lt;span class="normal"&gt; 3&lt;/span&gt;
&lt;span class="normal"&gt; 4&lt;/span&gt;
&lt;span class="normal"&gt; 5&lt;/span&gt;
&lt;span class="normal"&gt; 6&lt;/span&gt;
&lt;span class="normal"&gt; 7&lt;/span&gt;
&lt;span class="normal"&gt; 8&lt;/span&gt;
&lt;span class="normal"&gt; 9&lt;/span&gt;
&lt;span class="normal"&gt;10&lt;/span&gt;
&lt;span class="normal"&gt;11&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/td&gt;&lt;td class="code"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="nf"&gt;DisableLCD:&lt;/span&gt; &lt;span class="k"&gt;MACRO&lt;/span&gt;
    &lt;span class="ow"&gt;ld&lt;/span&gt; &lt;span class="nb"&gt;a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;rLCDC&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="ow"&gt;and&lt;/span&gt; &lt;span class="nb"&gt;a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mh"&gt;$ff&lt;/span&gt; &lt;span class="err"&gt;&amp;amp;&lt;/span&gt; &lt;span class="err"&gt;~&lt;/span&gt;&lt;span class="nv"&gt;LCDCF_ON&lt;/span&gt;
    &lt;span class="ow"&gt;ld&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;rLCDC&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="nb"&gt;a&lt;/span&gt;
&lt;span class="k"&gt;ENDM&lt;/span&gt;

&lt;span class="nf"&gt;EnableLCD:&lt;/span&gt; &lt;span class="k"&gt;MACRO&lt;/span&gt;
    &lt;span class="ow"&gt;ld&lt;/span&gt; &lt;span class="nb"&gt;a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;rLCDC&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="ow"&gt;or&lt;/span&gt; &lt;span class="nb"&gt;a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;LCDCF_ON&lt;/span&gt;
    &lt;span class="ow"&gt;ld&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;rLCDC&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="nb"&gt;a&lt;/span&gt;
&lt;span class="k"&gt;ENDM&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;p&gt;A breath of fresh&amp;nbsp;air!&lt;/p&gt;
&lt;p&gt;The &lt;code&gt;$ff &amp;amp;&lt;/code&gt; is necessary because the argument needs to fit in a byte, but rgbasm&amp;#8217;s integral preprocessor type is wider than a byte.  I suppose I could also use &lt;code&gt;LOW()&lt;/code&gt; here, or maybe there&amp;#8217;s some other more straightforward&amp;nbsp;solution.&lt;/p&gt;
&lt;h2 id="rearranging-the-buttons"&gt;&lt;a class="toclink" href="#rearranging-the-buttons"&gt;Rearranging the&amp;nbsp;buttons&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;In the &lt;a href="https://eev.ee/blog/2018/07/05/cheezball-rising-main-loop-input-and-a-game/"&gt;previous post&lt;/a&gt;, I read the button states and crammed them into a single byte.  I had a choice of whether to put the dpad low or the buttons low, but it didn&amp;#8217;t seem to matter, so I picked arbitrarily: buttons high, dpad&amp;nbsp;low.&lt;/p&gt;
&lt;p&gt;It turns out I chose wrong!  Also, it turns out there&amp;#8217;s a &amp;#8220;wrong&amp;#8221; here!  I&amp;#8217;ve heard two compelling reasons to do it the other way.  For one, &lt;code&gt;hardware.inc&lt;/code&gt; contains constants for the bit offsets of the buttons, and it assumes the dpad is high.  Why is this arbitrary data layout decision embedded in a list of &lt;em&gt;hardware&lt;/em&gt; constants?  Possibly for the second reason: on the &lt;span class="caps"&gt;GBA&lt;/span&gt;, input is available as a single word, and the lowest byte contains bits for all the buttons on the Game Boy — in the same order, with the dpad&amp;nbsp;high.&lt;/p&gt;
&lt;p&gt;So I&amp;#8217;m switching this around and using &lt;code&gt;hardware.inc&lt;/code&gt;&lt;span class="quo"&gt;&amp;#8216;&lt;/span&gt;s constants.  Easy&amp;nbsp;change.&lt;/p&gt;
&lt;h2 id="fixing-vblank"&gt;&lt;a class="toclink" href="#fixing-vblank"&gt;Fixing&amp;nbsp;vblank&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;My original approach to waiting for vblank seemed simple enough: loop until &lt;code&gt;vblank_flag&lt;/code&gt; is set, clear it, then continue&amp;nbsp;on.&lt;/p&gt;
&lt;p&gt;I&amp;#8217;ve made a slight oversight here: what if the main loop does take longer than a frame?  Then a vblank interrupt will fire in the middle of it and harmlessly set &lt;code&gt;vblank_flag&lt;/code&gt;.  But when the loop finally finishes and goes to wait for vblank again, the flag will &lt;em&gt;already be set&lt;/em&gt;, and it&amp;#8217;ll continue on immediately — regardless of the state of the screen!&amp;nbsp;Whoops.&lt;/p&gt;
&lt;p&gt;Again, the fix is simple: clear the flag &lt;em&gt;before&lt;/em&gt; beginning to&amp;nbsp;wait.&lt;/p&gt;
&lt;p&gt;And while I&amp;#8217;m at it, I see other uses for waiting for vblank in the near future, so I may as well pull this out into a&amp;nbsp;function.&lt;/p&gt;
&lt;table class="highlighttable"&gt;&lt;tr&gt;&lt;td class="linenos"&gt;&lt;div class="linenodiv"&gt;&lt;pre&gt;&lt;span class="normal"&gt; 1&lt;/span&gt;
&lt;span class="normal"&gt; 2&lt;/span&gt;
&lt;span class="normal"&gt; 3&lt;/span&gt;
&lt;span class="normal"&gt; 4&lt;/span&gt;
&lt;span class="normal"&gt; 5&lt;/span&gt;
&lt;span class="normal"&gt; 6&lt;/span&gt;
&lt;span class="normal"&gt; 7&lt;/span&gt;
&lt;span class="normal"&gt; 8&lt;/span&gt;
&lt;span class="normal"&gt; 9&lt;/span&gt;
&lt;span class="normal"&gt;10&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/td&gt;&lt;td class="code"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="c1"&gt;; idle until next vblank&lt;/span&gt;
&lt;span class="nf"&gt;wait_for_vblank:&lt;/span&gt;
    &lt;span class="ow"&gt;xor&lt;/span&gt; &lt;span class="nb"&gt;a&lt;/span&gt;                       &lt;span class="c1"&gt;; clear the vblank flag&lt;/span&gt;
    &lt;span class="ow"&gt;ld&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;vblank_flag&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="nb"&gt;a&lt;/span&gt;
&lt;span class="nf"&gt;.vblank_loop:&lt;/span&gt;
    &lt;span class="ow"&gt;halt&lt;/span&gt;                        &lt;span class="c1"&gt;; wait for interrupt&lt;/span&gt;
    &lt;span class="ow"&gt;ld&lt;/span&gt; &lt;span class="nb"&gt;a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;vblank_flag&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;         &lt;span class="c1"&gt;; was it a vblank interrupt?&lt;/span&gt;
    &lt;span class="ow"&gt;and&lt;/span&gt; &lt;span class="nb"&gt;a&lt;/span&gt;
    &lt;span class="ow"&gt;jr&lt;/span&gt; &lt;span class="nv"&gt;z&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="err"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;vblank_loop&lt;/span&gt;          &lt;span class="c1"&gt;; if not, keep waiting&lt;/span&gt;
    &lt;span class="ow"&gt;ret&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;p&gt;But wait!  We&amp;#8217;re not quite done yet; the comments have pointed out another oversight.  What happens if a vblank interrupt occurs &lt;em&gt;between&lt;/em&gt; the first &lt;code&gt;ld&lt;/code&gt; and the &lt;code&gt;halt&lt;/code&gt;?&lt;/p&gt;
&lt;table class="highlighttable"&gt;&lt;tr&gt;&lt;td class="linenos"&gt;&lt;div class="linenodiv"&gt;&lt;pre&gt;&lt;span class="normal"&gt;1&lt;/span&gt;
&lt;span class="normal"&gt;2&lt;/span&gt;
&lt;span class="normal"&gt;3&lt;/span&gt;
&lt;span class="normal"&gt;4&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/td&gt;&lt;td class="code"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;    &lt;span class="ow"&gt;xor&lt;/span&gt; &lt;span class="nb"&gt;a&lt;/span&gt;
    &lt;span class="ow"&gt;ld&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;vblank_flag&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="nb"&gt;a&lt;/span&gt;
    &lt;span class="c1"&gt;; vblank interrupt occurs HERE, setting the flag to 1&lt;/span&gt;
    &lt;span class="ow"&gt;halt&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;p&gt;The flag will already have been set, but then we&amp;#8217;ll halt anyway and wait for the &lt;em&gt;next&lt;/em&gt; interrupt.  If that next interrupt is another vblank, all is fine.  If it&amp;#8217;s something else — like the timer, say — then the following code will see the flag is set and return immediately, even though it&amp;#8217;s been some amount of time and we might actually be in the middle of a&amp;nbsp;frame!&lt;/p&gt;
&lt;p&gt;The same problem occurs within the loop: if we get an unrelated interrupt, &lt;code&gt;ld&lt;/code&gt; the flag while it&amp;#8217;s cleared, and then a vblank interrupt sets it before we test it, we&amp;#8217;ll jump back up and wait for another interrupt which we&amp;#8217;ll then misidentify as a&amp;nbsp;vblank.&lt;/p&gt;
&lt;p&gt;These very precise circumstances are fairly unlikely, but that&amp;#8217;s not good enough for me.  Let&amp;#8217;s fix&amp;nbsp;it:&lt;/p&gt;
&lt;table class="highlighttable"&gt;&lt;tr&gt;&lt;td class="linenos"&gt;&lt;div class="linenodiv"&gt;&lt;pre&gt;&lt;span class="normal"&gt; 1&lt;/span&gt;
&lt;span class="normal"&gt; 2&lt;/span&gt;
&lt;span class="normal"&gt; 3&lt;/span&gt;
&lt;span class="normal"&gt; 4&lt;/span&gt;
&lt;span class="normal"&gt; 5&lt;/span&gt;
&lt;span class="normal"&gt; 6&lt;/span&gt;
&lt;span class="normal"&gt; 7&lt;/span&gt;
&lt;span class="normal"&gt; 8&lt;/span&gt;
&lt;span class="normal"&gt; 9&lt;/span&gt;
&lt;span class="normal"&gt;10&lt;/span&gt;
&lt;span class="normal"&gt;11&lt;/span&gt;
&lt;span class="normal"&gt;12&lt;/span&gt;
&lt;span class="normal"&gt;13&lt;/span&gt;
&lt;span class="normal"&gt;14&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/td&gt;&lt;td class="code"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="c1"&gt;; idle until next vblank&lt;/span&gt;
&lt;span class="nf"&gt;wait_for_vblank:&lt;/span&gt;
    &lt;span class="ow"&gt;xor&lt;/span&gt; &lt;span class="nb"&gt;a&lt;/span&gt;                       &lt;span class="c1"&gt;; clear the vblank flag&lt;/span&gt;
    &lt;span class="ow"&gt;di&lt;/span&gt;                          &lt;span class="c1"&gt;; avoid irq race after this ld&lt;/span&gt;
    &lt;span class="ow"&gt;ld&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;vblank_flag&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="nb"&gt;a&lt;/span&gt;
&lt;span class="nf"&gt;.vblank_loop:&lt;/span&gt;
    &lt;span class="ow"&gt;ei&lt;/span&gt;
    &lt;span class="ow"&gt;halt&lt;/span&gt;                        &lt;span class="c1"&gt;; wait for interrupt&lt;/span&gt;
    &lt;span class="ow"&gt;di&lt;/span&gt;
    &lt;span class="ow"&gt;ld&lt;/span&gt; &lt;span class="nb"&gt;a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;vblank_flag&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;         &lt;span class="c1"&gt;; was it a vblank interrupt?&lt;/span&gt;
    &lt;span class="ow"&gt;and&lt;/span&gt; &lt;span class="nb"&gt;a&lt;/span&gt;
    &lt;span class="ow"&gt;jr&lt;/span&gt; &lt;span class="nv"&gt;z&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="err"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;vblank_loop&lt;/span&gt;          &lt;span class="c1"&gt;; if not, keep waiting&lt;/span&gt;
    &lt;span class="ow"&gt;ei&lt;/span&gt;
    &lt;span class="ow"&gt;ret&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;p&gt;Now interrupts are disabled before we read the flag, and only enabled again immediately before the &lt;code&gt;halt&lt;/code&gt;.  (An interrupt request sent while interrupts are disabled won&amp;#8217;t be thrown on the floor; it&amp;#8217;s merely delayed until interrupts are enabled&amp;nbsp;again.)&lt;/p&gt;
&lt;p&gt;&lt;span class="dquo"&gt;&amp;#8220;&lt;/span&gt;Hang on,&amp;#8221; I hear you cry.  &amp;#8220;Haven&amp;#8217;t you just moved the goalposts?  Can&amp;#8217;t an interrupt now happen between &lt;code&gt;ei&lt;/code&gt; and &lt;code&gt;halt&lt;/code&gt;, just like&amp;nbsp;before?&amp;#8221;&lt;/p&gt;
&lt;p&gt;Ah, but I have one final piece of arcane trivia up my sleeve: &lt;code&gt;ei&lt;/code&gt; has a built-in &lt;em&gt;delay&lt;/em&gt; and doesn&amp;#8217;t take effect until after the next instruction.  Possibly for this exact reason!  This should, fingers crossed, be completely&amp;nbsp;bulletproof.&lt;/p&gt;
&lt;h2 id="copy-function"&gt;&lt;a class="toclink" href="#copy-function"&gt;Copy&amp;nbsp;function&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;So far, I&amp;#8217;ve done an awful lot of runtime copying by using the preprocessor.  Consider the code for copying the &lt;span class="caps"&gt;DMA&lt;/span&gt; routine into &lt;span class="caps"&gt;HRAM&lt;/span&gt;:&lt;/p&gt;
&lt;table class="highlighttable"&gt;&lt;tr&gt;&lt;td class="linenos"&gt;&lt;div class="linenodiv"&gt;&lt;pre&gt;&lt;span class="normal"&gt;1&lt;/span&gt;
&lt;span class="normal"&gt;2&lt;/span&gt;
&lt;span class="normal"&gt;3&lt;/span&gt;
&lt;span class="normal"&gt;4&lt;/span&gt;
&lt;span class="normal"&gt;5&lt;/span&gt;
&lt;span class="normal"&gt;6&lt;/span&gt;
&lt;span class="normal"&gt;7&lt;/span&gt;
&lt;span class="normal"&gt;8&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/td&gt;&lt;td class="code"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;    &lt;span class="c1"&gt;; Copy the little DMA routine into high RAM&lt;/span&gt;
    &lt;span class="ow"&gt;ld&lt;/span&gt; &lt;span class="nb"&gt;bc&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;dma_copy&lt;/span&gt;
    &lt;span class="ow"&gt;ld&lt;/span&gt; &lt;span class="nb"&gt;hl&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mh"&gt;$ff80&lt;/span&gt;
    &lt;span class="k"&gt;REPT dma_copy_end - dma_copy&lt;/span&gt;
    &lt;span class="ow"&gt;ld&lt;/span&gt; &lt;span class="nb"&gt;a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;bc&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="ow"&gt;inc&lt;/span&gt; &lt;span class="nb"&gt;bc&lt;/span&gt;
    &lt;span class="ow"&gt;ld&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;hl&lt;/span&gt;&lt;span class="p"&gt;+],&lt;/span&gt; &lt;span class="nb"&gt;a&lt;/span&gt;
    &lt;span class="k"&gt;ENDR&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;p&gt;This will repeat the &lt;code&gt;ld&lt;/code&gt;/&lt;code&gt;inc&lt;/code&gt;/&lt;code&gt;ld&lt;/code&gt; dance 13 times in the built &lt;span class="caps"&gt;ROM&lt;/span&gt;.  Which is fine, except that I&amp;#8217;m about to have places where I do &lt;em&gt;much more&lt;/em&gt; copying, and there&amp;#8217;s only so much space in the &lt;span class="caps"&gt;ROM&lt;/span&gt;, and this is kind of ridiculous.  So I &lt;em&gt;guess&lt;/em&gt; I will finally write a copy&amp;nbsp;function.&lt;/p&gt;
&lt;p&gt;I&amp;#8217;m calling it &lt;code&gt;copy&lt;/code&gt;, not &lt;code&gt;memcpy&lt;/code&gt;.  What else am I going to copy, if not&amp;nbsp;memory?&lt;/p&gt;
&lt;p&gt;Attempt number 1 looked like&amp;nbsp;this:&lt;/p&gt;
&lt;table class="highlighttable"&gt;&lt;tr&gt;&lt;td class="linenos"&gt;&lt;div class="linenodiv"&gt;&lt;pre&gt;&lt;span class="normal"&gt;1&lt;/span&gt;
&lt;span class="normal"&gt;2&lt;/span&gt;
&lt;span class="normal"&gt;3&lt;/span&gt;
&lt;span class="normal"&gt;4&lt;/span&gt;
&lt;span class="normal"&gt;5&lt;/span&gt;
&lt;span class="normal"&gt;6&lt;/span&gt;
&lt;span class="normal"&gt;7&lt;/span&gt;
&lt;span class="normal"&gt;8&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/td&gt;&lt;td class="code"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="c1"&gt;; copy d bytes from bc to hl&lt;/span&gt;
&lt;span class="nf"&gt;copy:&lt;/span&gt;
    &lt;span class="ow"&gt;ld&lt;/span&gt; &lt;span class="nb"&gt;a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;bc&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="ow"&gt;inc&lt;/span&gt; &lt;span class="nb"&gt;bc&lt;/span&gt;
    &lt;span class="ow"&gt;ld&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;hl&lt;/span&gt;&lt;span class="p"&gt;+],&lt;/span&gt; &lt;span class="nb"&gt;a&lt;/span&gt;
    &lt;span class="ow"&gt;dec&lt;/span&gt; &lt;span class="nb"&gt;d&lt;/span&gt;
    &lt;span class="ow"&gt;jr&lt;/span&gt; &lt;span class="nv"&gt;z&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;copy&lt;/span&gt;
    &lt;span class="ow"&gt;ret&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;p&gt;I was then informed that it&amp;#8217;s more idiomatic to use &lt;code&gt;de&lt;/code&gt; as the source address and &lt;code&gt;c&lt;/code&gt; as the count, possibly for some reason relating to the &lt;span class="caps"&gt;NES&lt;/span&gt; or &lt;span class="caps"&gt;SNES&lt;/span&gt;?  I don&amp;#8217;t remember.  I&amp;#8217;m totally on board for using &lt;code&gt;c&lt;/code&gt; to mean a count, though, and started doing that&amp;nbsp;elsewhere.&lt;/p&gt;
&lt;p&gt;I went to change that, and actually make use of this function, and lo!  I discovered a colossal bug.  That last line, &lt;code&gt;jr z, copy&lt;/code&gt;, will loop only if &lt;code&gt;d&lt;/code&gt; was just decremented to zero.  So this function will only ever copy one byte, unless you &lt;em&gt;asked&lt;/em&gt; to copy only one byte, in which case it copies&amp;nbsp;two.&lt;/p&gt;
&lt;p&gt;This is not the first time I&amp;#8217;ve gotten a condition backwards.  I&amp;#8217;ll get used to it eventually, I&amp;#8217;m&amp;nbsp;sure.&lt;/p&gt;
&lt;p&gt;Oh, one other minor problem: if you ask to copy zero bytes, you&amp;#8217;ll actually copy 256, since the zero check only comes after the decrement.  (This is a recurring annoyance, actually, and makes &lt;code&gt;while&lt;/code&gt; loops surprisingly clumsy to express.)  So far I&amp;#8217;ve only ever needed to copy a constant amount, so this hasn&amp;#8217;t been a problem, but&amp;#8230;  I&amp;#8217;ll just leave a comment pretending it&amp;#8217;s a&amp;nbsp;feature.&lt;/p&gt;
&lt;table class="highlighttable"&gt;&lt;tr&gt;&lt;td class="linenos"&gt;&lt;div class="linenodiv"&gt;&lt;pre&gt;&lt;span class="normal"&gt;1&lt;/span&gt;
&lt;span class="normal"&gt;2&lt;/span&gt;
&lt;span class="normal"&gt;3&lt;/span&gt;
&lt;span class="normal"&gt;4&lt;/span&gt;
&lt;span class="normal"&gt;5&lt;/span&gt;
&lt;span class="normal"&gt;6&lt;/span&gt;
&lt;span class="normal"&gt;7&lt;/span&gt;
&lt;span class="normal"&gt;8&lt;/span&gt;
&lt;span class="normal"&gt;9&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/td&gt;&lt;td class="code"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="c1"&gt;; copy c bytes from de to hl&lt;/span&gt;
&lt;span class="c1"&gt;; NOTE: c = 0 means to copy 256 bytes!&lt;/span&gt;
&lt;span class="nf"&gt;copy:&lt;/span&gt;
    &lt;span class="ow"&gt;ld&lt;/span&gt; &lt;span class="nb"&gt;a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;de&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="ow"&gt;inc&lt;/span&gt; &lt;span class="nb"&gt;de&lt;/span&gt;
    &lt;span class="ow"&gt;ld&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;hl&lt;/span&gt;&lt;span class="p"&gt;+],&lt;/span&gt; &lt;span class="nb"&gt;a&lt;/span&gt;
    &lt;span class="ow"&gt;dec&lt;/span&gt; &lt;span class="nb"&gt;c&lt;/span&gt;
    &lt;span class="ow"&gt;jr&lt;/span&gt; &lt;span class="nv"&gt;nz&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;copy&lt;/span&gt;
    &lt;span class="ow"&gt;ret&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;p&gt;And here it is in&amp;nbsp;action:&lt;/p&gt;
&lt;table class="highlighttable"&gt;&lt;tr&gt;&lt;td class="linenos"&gt;&lt;div class="linenodiv"&gt;&lt;pre&gt;&lt;span class="normal"&gt;1&lt;/span&gt;
&lt;span class="normal"&gt;2&lt;/span&gt;
&lt;span class="normal"&gt;3&lt;/span&gt;
&lt;span class="normal"&gt;4&lt;/span&gt;
&lt;span class="normal"&gt;5&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/td&gt;&lt;td class="code"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;    &lt;span class="c1"&gt;; Copy the little DMA routine into high RAM&lt;/span&gt;
    &lt;span class="ow"&gt;ld&lt;/span&gt; &lt;span class="nb"&gt;de&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;dma_copy&lt;/span&gt;
    &lt;span class="ow"&gt;ld&lt;/span&gt; &lt;span class="nb"&gt;hl&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mh"&gt;$FF80&lt;/span&gt;
    &lt;span class="ow"&gt;ld&lt;/span&gt; &lt;span class="nb"&gt;c&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;dma_copy_end&lt;/span&gt; &lt;span class="p"&gt;-&lt;/span&gt; &lt;span class="nv"&gt;dma_copy&lt;/span&gt;
    &lt;span class="ow"&gt;call&lt;/span&gt; &lt;span class="nv"&gt;copy&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;p&gt;Cool.&lt;/p&gt;
&lt;p&gt;Of course, this is now significantly slower than the original unrolled version.  The original took 13 × (2 + 2 + 2) = 78 cycles; the function adds 6 cycles for the &lt;code&gt;call&lt;/code&gt;, 4 cycles for the &lt;code&gt;ret&lt;/code&gt;, and 13 × (1 + 3) = 52 for the counting and jumping.  As &lt;code&gt;c&lt;/code&gt; goes to infinity, the function takes about ⅔ longer than&amp;nbsp;unrolling.&lt;/p&gt;
&lt;p&gt;If I feel like it, I could mitigate this somewhat by &lt;em&gt;partially&lt;/em&gt; unrolling.  First I&amp;#8217;d mask off some lower bits of &lt;code&gt;c&lt;/code&gt; — say, the lowest two — and copy that many bytes.  Now the amount of copying left is a multiple of four, so I could shift &lt;code&gt;c&lt;/code&gt; right twice and have another loop that copies &lt;em&gt;four&lt;/em&gt; bytes at a time, amortizing the cost of the decrement and&amp;nbsp;jump.&lt;/p&gt;
&lt;p&gt;It&amp;#8217;s not urgent enough for me to want to bother yet, and it&amp;#8217;ll make relatively little difference for small copies like this &lt;span class="caps"&gt;DMA&lt;/span&gt; one, but I&amp;#8217;m strongly considering it for copying a 16-bit&amp;nbsp;amount.&lt;/p&gt;
&lt;h2 id="reset-vectors"&gt;&lt;a class="toclink" href="#reset-vectors"&gt;Reset&amp;nbsp;vectors&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Now I have a couple utility functions like &lt;code&gt;copy&lt;/code&gt; and &lt;code&gt;wait_for_vblank&lt;/code&gt;.  I don&amp;#8217;t really care where they go, so I put them in their own &lt;code&gt;SECTION&lt;/code&gt; and let the linker figure it&amp;nbsp;out.&lt;/p&gt;
&lt;p&gt;It took a while for me to notice &lt;em&gt;where&lt;/em&gt;, exactly, the linker had put them: at $0000!  These functions are small, and I have nothing explicitly placed before the interrupt handlers (which begin at $0040), so &lt;code&gt;rgblink&lt;/code&gt; saw some empty space and filled&amp;nbsp;it.&lt;/p&gt;
&lt;p&gt;The thing is, the Game Boy has eight instructions of the form &lt;code&gt;rst $xx&lt;/code&gt; that act as fast calls — each one jumps to a fixed low address (a &amp;#8220;reset vector&amp;#8221;), using less time and space than a &lt;code&gt;call&lt;/code&gt; would.  And those fixed &lt;code&gt;$xx&lt;/code&gt; addresses are…  &lt;code&gt;$00&lt;/code&gt;, and every eight bytes&amp;nbsp;afterwards.&lt;/p&gt;
&lt;p&gt;I don&amp;#8217;t have any immediate use for these — eight bytes isn&amp;#8217;t a lot, though I guess &lt;code&gt;copy&lt;/code&gt; could fit in there — but I probably don&amp;#8217;t want arbitrary code ending up where they go, so for now I&amp;#8217;ll stub them out like I stubbed out the interrupt&amp;nbsp;handlers.&lt;/p&gt;
&lt;p&gt;(I have been advised of one very good use for reset vectors: putting a crash handler at $38.  Why?  Because &lt;code&gt;rst $38&lt;/code&gt; is encoded as $ff, which is a fairly common byte to encounter if you accidentally jump into garbage.  A lot of the Game Boy&amp;#8217;s &lt;span class="caps"&gt;RAM&lt;/span&gt; is even initialized to $ff at&amp;nbsp;startup.)&lt;/p&gt;
&lt;h2 id="idioms"&gt;&lt;a class="toclink" href="#idioms"&gt;Idioms&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;I&amp;#8217;m still discovering what&amp;#8217;s considered idiomatic, but here are a couple&amp;nbsp;tidbits.&lt;/p&gt;
&lt;p&gt;The set of instructions is a little scattershot as far as arguments go.  Several times early on, I wrote stuff like&amp;nbsp;this:&lt;/p&gt;
&lt;table class="highlighttable"&gt;&lt;tr&gt;&lt;td class="linenos"&gt;&lt;div class="linenodiv"&gt;&lt;pre&gt;&lt;span class="normal"&gt;1&lt;/span&gt;
&lt;span class="normal"&gt;2&lt;/span&gt;
&lt;span class="normal"&gt;3&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/td&gt;&lt;td class="code"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;  &lt;span class="ow"&gt;ld&lt;/span&gt; &lt;span class="nb"&gt;hl&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;some_address&lt;/span&gt;
  &lt;span class="ow"&gt;ld&lt;/span&gt; &lt;span class="nb"&gt;a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;133&lt;/span&gt;
  &lt;span class="ow"&gt;ld&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;hl&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="nb"&gt;a&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;p&gt;But I overlooked that there are instructions for both &lt;code&gt;ld [hl], n8&lt;/code&gt; and &lt;code&gt;ld [n16], a&lt;/code&gt;, so the above can be reduced to two lines.  There&amp;#8217;s no such thing as &lt;code&gt;ld [n16], n8&lt;/code&gt;,&amp;nbsp;though.&lt;/p&gt;
&lt;p&gt;A surprising number of instructions can use &lt;code&gt;[hl]&lt;/code&gt; directly as an operand — even &lt;code&gt;inc&lt;/code&gt; and &lt;code&gt;dec&lt;/code&gt;, combining fetch/mutate/store into a single&amp;nbsp;instruction.&lt;/p&gt;
&lt;p&gt;As I mentioned before, due to a bug, every &lt;code&gt;halt&lt;/code&gt; should be followed by a &lt;code&gt;nop&lt;/code&gt; — but &lt;code&gt;rgbasm&lt;/code&gt; already does this by default, so I removed all the extraneous &lt;code&gt;nop&lt;/code&gt;s.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;xor a&lt;/code&gt; is twice as short and twice as fast as &lt;code&gt;ld a, 0&lt;/code&gt;.  I mean, we&amp;#8217;re talking about a single byte and single cycle here, but no reason &lt;em&gt;not&lt;/em&gt;&amp;nbsp;to.&lt;/p&gt;
&lt;p&gt;(&lt;code&gt;xor a&lt;/code&gt; really means &lt;code&gt;xor a, a&lt;/code&gt;, but since every boolean op instruction takes &lt;code&gt;a&lt;/code&gt; as the first argument anyway, it can be omitted.  I don&amp;#8217;t like to omit it in most cases, since &lt;code&gt;xor b&lt;/code&gt; doesn&amp;#8217;t mention &lt;code&gt;a&lt;/code&gt; at all and that seems misleading, but it feels appropriate when combining &lt;code&gt;a&lt;/code&gt; with&amp;nbsp;itself.)&lt;/p&gt;
&lt;p&gt;&lt;code&gt;or a&lt;/code&gt; (equivalently, &lt;code&gt;and a&lt;/code&gt;) is a quick way to test whether &lt;code&gt;a&lt;/code&gt; is zero, since boolean ops set the zero&amp;nbsp;flag.&lt;/p&gt;
&lt;h2 id="color"&gt;&lt;a class="toclink" href="#color"&gt;Color&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;This is neither here nor there, but since this post began with emulator differences, here&amp;#8217;s another&amp;nbsp;one.&lt;/p&gt;
&lt;p&gt;The screen you&amp;#8217;re reading this on is almost certainly backlit, but the original Game Boy Color screen was &lt;em&gt;not&lt;/em&gt;.  A fully white pixel on a Game Boy Color is &lt;em&gt;turned off&lt;/em&gt; — it&amp;#8217;s the color of the screen itself, in which you can probably see your own&amp;nbsp;reflection.&lt;/p&gt;
&lt;p&gt;Which raises a tricky question: &lt;em&gt;what color is that&lt;/em&gt;?  The game thinks it&amp;#8217;s pure white, but the screen was a sort of pale yellow.  So how should it be rendered in an emulator, on a modern backlit &lt;span class="caps"&gt;LCD&lt;/span&gt;&amp;nbsp;monitor?&lt;/p&gt;
&lt;p&gt;Compounding this problem is that Game Boy Color games can also run on the Game Boy Advance, which showed the colors yet slightly differently.  And, of course, even monitors may be calibrated differently, in which case it all goes out the&amp;nbsp;window.&lt;/p&gt;
&lt;p&gt;It&amp;#8217;s interesting to see different emulators&amp;#8217; opinions of how to render&amp;nbsp;color:&lt;/p&gt;
&lt;div class="prose-full-illustration"&gt;
&lt;img src="https://eev.ee/media/cheezball/03b-emulator-colors.png" alt="The same screenshot, seen in several different emulators with different color schemes"&gt;
&lt;/div&gt;

&lt;p&gt;This is exactly the same &lt;span class="caps"&gt;ROM&lt;/span&gt;.  The top left is mGBA out of the box, which shows colors completely unaltered — usually fairly saturated.  The top right is mGBA with its &amp;#8220;gba-colors&amp;#8221; shader enabled, which is supposed to replicate how colors appear on a &lt;span class="caps"&gt;GBA&lt;/span&gt; screen, but seems passingly similar to a &lt;span class="caps"&gt;GBC&lt;/span&gt; too.  Then on the bottom are two emulators renowned for their accuracy, here wildly disagreeing with each&amp;nbsp;other.&lt;/p&gt;
&lt;p&gt;My Game Boy Color is currently in a box somewhere, and until I can find it, I can&amp;#8217;t be sure who&amp;#8217;s closer.  All of these are perfectly fine interpretations of the same art,&amp;nbsp;though.&lt;/p&gt;
&lt;p&gt;I may or may not use the &amp;#8220;gba-colors&amp;#8221; shader, and may or may not fiddle with mGBA&amp;#8217;s color settings over time.  If the colors vary a bit in future screenshots, that&amp;#8217;s probably&amp;nbsp;why.&lt;/p&gt;
&lt;h2 id="to-be-continued"&gt;&lt;a class="toclink" href="#to-be-continued"&gt;To be&amp;nbsp;continued&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;This post doesn&amp;#8217;t really correspond to a particular commit very well, since it&amp;#8217;s all little stuff I did here and there.  I hope you&amp;#8217;ve enjoyed the breather, because it&amp;#8217;s all downhill from here.  In a good way, I mean.  Like a&amp;nbsp;rollercoaster.&lt;/p&gt;
&lt;p&gt;Next time: &lt;a href="https://eev.ee/blog/2018/07/15/cheezball-rising-maps-and-sprites/"&gt;map and sprite loading&lt;/a&gt;, which will explain how I got from grass to the moon texture in the screenshots&amp;nbsp;above!&lt;/p&gt;</content><category term="blog"></category><category term="tech"></category><category term="gamedev"></category><category term="cheezball rising"></category></entry><entry><title>Cheezball Rising: Main loop, input, and a game</title><link href="https://eev.ee/blog/2018/07/05/cheezball-rising-main-loop-input-and-a-game/" rel="alternate"></link><published>2018-07-05T09:41:00-07:00</published><updated>2018-07-05T09:41:00-07:00</updated><author><name>Eevee</name></author><id>tag:eev.ee,2018-07-05:/blog/2018/07/05/cheezball-rising-main-loop-input-and-a-game/</id><summary type="html">&lt;p&gt;This is a series about &lt;a href="https://github.com/eevee/anise-cheezball-rising"&gt;&lt;strong&gt;Star Anise Chronicles: Cheezball Rising&lt;/strong&gt;&lt;/a&gt;, an expansive adventure game about my cat for the Game Boy Color.  Follow along as I struggle to make something with this bleeding-edge console!&lt;/p&gt;
&lt;p&gt;GitHub has &lt;a href="https://github.com/eevee/anise-cheezball-rising/releases"&gt;intermittent prebuilt ROMs&lt;/a&gt;, or you can get them a week early &lt;a href="https://www.patreon.com/eevee/posts?tag=cheezball%20rising"&gt;on Patreon&lt;/a&gt; if you pledge $4.  More details in the &lt;a href="https://github.com/eevee/anise-cheezball-rising"&gt;&lt;span class="caps"&gt;README&lt;/span&gt;&lt;/a&gt;!&lt;/p&gt;
&lt;hr/&gt;
&lt;p&gt;In this issue, I fill in the remaining bits necessary to have something that &lt;em&gt;looks&lt;/em&gt; like a game.&lt;/p&gt;
&lt;p&gt;Previously: &lt;a href="https://eev.ee/blog/2018/06/21/cheezball-rising-drawing-a-sprite/"&gt;drawing a sprite&lt;/a&gt;.&lt;br/&gt;
Next: &lt;a href="https://eev.ee/blog/2018/07/13/cheezball-rising-spring-cleaning/"&gt;a little spring cleaning&lt;/a&gt;.&lt;/p&gt;
</summary><content type="html">&lt;p&gt;This is a series about &lt;a href="https://github.com/eevee/anise-cheezball-rising"&gt;&lt;strong&gt;Star Anise Chronicles: Cheezball Rising&lt;/strong&gt;&lt;/a&gt;, an expansive adventure game about my cat for the Game Boy Color.  Follow along as I struggle to make something with this bleeding-edge&amp;nbsp;console!&lt;/p&gt;
&lt;p&gt;GitHub has &lt;a href="https://github.com/eevee/anise-cheezball-rising/releases"&gt;intermittent prebuilt ROMs&lt;/a&gt;, or you can get them a week early &lt;a href="https://www.patreon.com/eevee/posts?tag=cheezball%20rising"&gt;on Patreon&lt;/a&gt; if you pledge $4.  More details in the &lt;a href="https://github.com/eevee/anise-cheezball-rising"&gt;&lt;span class="caps"&gt;README&lt;/span&gt;&lt;/a&gt;!&lt;/p&gt;
&lt;hr /&gt;
&lt;p&gt;In this issue, I fill in the remaining bits necessary to have something that &lt;em&gt;looks&lt;/em&gt; like a&amp;nbsp;game.&lt;/p&gt;
&lt;p&gt;Previously: &lt;a href="https://eev.ee/blog/2018/06/21/cheezball-rising-drawing-a-sprite/"&gt;drawing a sprite&lt;/a&gt;.&lt;br /&gt;
Next: &lt;a href="https://eev.ee/blog/2018/07/13/cheezball-rising-spring-cleaning/"&gt;a little spring cleaning&lt;/a&gt;.&lt;/p&gt;


&lt;h2 id="recap"&gt;&lt;a class="toclink" href="#recap"&gt;Recap&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;So far, I have&amp;nbsp;this.&lt;/p&gt;
&lt;div class="prose-full-illustration"&gt;
&lt;img src="https://eev.ee/media/cheezball/01d-first-object.png" alt="A very gaudy striped background with half a cat on top"&gt;
&lt;/div&gt;

&lt;p&gt;It took unfathomable amounts of effort, but it&amp;#8217;s something!  Now to improve this from a static image to something a bit more&amp;nbsp;game-like.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Quick note&lt;/strong&gt;: I&amp;#8217;ve been advised to use the de facto standard &lt;a href="https://github.com/tobiasvl/hardware.inc"&gt;&lt;code&gt;hardware.inc&lt;/code&gt;&lt;/a&gt; file, which gives symbolic names to all the registers and some of the flags they use.  I hadn&amp;#8217;t introduced it yet while doing the work described in this post, but for the sake of readability, I&amp;#8217;m going to &lt;em&gt;pretend&lt;/em&gt; I did and use that file&amp;#8217;s constants in the code snippets&amp;nbsp;here.&lt;/p&gt;
&lt;h2 id="interrupts"&gt;&lt;a class="toclink" href="#interrupts"&gt;Interrupts&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;To get much further, I need to deal with &lt;em&gt;interrupts&lt;/em&gt;.  And to explain interrupts, I need to briefly explain &lt;em&gt;calls&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;Assembly doesn&amp;#8217;t really have functions, only addresses and jumps.  That said, the Game Boy does have &lt;code&gt;call&lt;/code&gt; and &lt;code&gt;ret&lt;/code&gt; instructions.  A &lt;code&gt;call&lt;/code&gt; will push the &lt;span class="caps"&gt;PC&lt;/span&gt; register (&lt;em&gt;program counter&lt;/em&gt;, the address of the current instruction) onto the stack and perform a jump; a &lt;code&gt;ret&lt;/code&gt; will pop into the &lt;span class="caps"&gt;PC&lt;/span&gt; register, effectively jumping back to the source of the &lt;code&gt;call&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;There are no arguments, return values, or scoping; input and output must be mediated by each function, usually via registers.  Of course, since registers are global, a &amp;#8220;function&amp;#8221; might trample over their values in the course of whatever work it does.  A function can manually &lt;code&gt;push&lt;/code&gt; and &lt;code&gt;pop&lt;/code&gt; 16-bit register pairs to preserve their values, or leave it up to the caller for speed/space reasons.  All the conventions are free for me to invent or ignore.  A &amp;#8220;function&amp;#8221; can even jump directly to another function and piggyback on the second function&amp;#8217;s &lt;code&gt;ret&lt;/code&gt;, kind of like Perl&amp;#8217;s &lt;code&gt;goto &amp;amp;sub&lt;/code&gt;…  which I realize is probably less common knowledge than how call/return work in&amp;nbsp;assembly.&lt;/p&gt;
&lt;p&gt;Interrupts, then, are calls that can happen at any time.  When one of a handful of conditions occurs, the &lt;span class="caps"&gt;CPU&lt;/span&gt; can immediately (or, rather, just before the next instruction) call an interrupt &lt;em&gt;handler&lt;/em&gt;, regardless of what it was already doing.  When the handler returns, execution resumes in the interrupted&amp;nbsp;code.&lt;/p&gt;
&lt;p&gt;Of course, since they might be called &lt;em&gt;anywhere&lt;/em&gt;, interrupt handlers need to be very careful about preserving the &lt;span class="caps"&gt;CPU&lt;/span&gt; state.  Pushing &lt;code&gt;af&lt;/code&gt; is especially important (and this is the one place where &lt;code&gt;af&lt;/code&gt; is used as a pair), because &lt;code&gt;a&lt;/code&gt; is necessary for getting almost anything done, and &lt;code&gt;f&lt;/code&gt; holds the flags which most instructions will invisibly&amp;nbsp;trample.&lt;/p&gt;
&lt;p&gt;Naturally, I completely forgot about this the first time&amp;nbsp;around.&lt;/p&gt;
&lt;p&gt;The Game Boy has five interrupts, each with a handler at a fixed address very low in &lt;span class="caps"&gt;ROM&lt;/span&gt;.  Each handler only has room for eight bytes&amp;#8217; worth of instructions, which is enough to do a very tiny amount of work — or to just jump&amp;nbsp;elsewhere.&lt;/p&gt;
&lt;p&gt;A good start is to populate each one with only the &lt;code&gt;reti&lt;/code&gt; instruction, which returns as usual &lt;em&gt;and&lt;/em&gt; re-enables interrupts.  The &lt;span class="caps"&gt;CPU&lt;/span&gt; disables interrupts when it calls an interrupt handler (so they thankfully can&amp;#8217;t interrupt themselves), and returning with only &lt;code&gt;ret&lt;/code&gt; will leave them&amp;nbsp;disabled.&lt;/p&gt;
&lt;p&gt;Naturally, I completely forgot about this the first time&amp;nbsp;around.&lt;/p&gt;
&lt;table class="highlighttable"&gt;&lt;tr&gt;&lt;td class="linenos"&gt;&lt;div class="linenodiv"&gt;&lt;pre&gt;&lt;span class="normal"&gt; 1&lt;/span&gt;
&lt;span class="normal"&gt; 2&lt;/span&gt;
&lt;span class="normal"&gt; 3&lt;/span&gt;
&lt;span class="normal"&gt; 4&lt;/span&gt;
&lt;span class="normal"&gt; 5&lt;/span&gt;
&lt;span class="normal"&gt; 6&lt;/span&gt;
&lt;span class="normal"&gt; 7&lt;/span&gt;
&lt;span class="normal"&gt; 8&lt;/span&gt;
&lt;span class="normal"&gt; 9&lt;/span&gt;
&lt;span class="normal"&gt;10&lt;/span&gt;
&lt;span class="normal"&gt;11&lt;/span&gt;
&lt;span class="normal"&gt;12&lt;/span&gt;
&lt;span class="normal"&gt;13&lt;/span&gt;
&lt;span class="normal"&gt;14&lt;/span&gt;
&lt;span class="normal"&gt;15&lt;/span&gt;
&lt;span class="normal"&gt;16&lt;/span&gt;
&lt;span class="normal"&gt;17&lt;/span&gt;
&lt;span class="normal"&gt;18&lt;/span&gt;
&lt;span class="normal"&gt;19&lt;/span&gt;
&lt;span class="normal"&gt;20&lt;/span&gt;
&lt;span class="normal"&gt;21&lt;/span&gt;
&lt;span class="normal"&gt;22&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/td&gt;&lt;td class="code"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="c1"&gt;; Interrupt handlers&lt;/span&gt;
&lt;span class="k"&gt;SECTION &amp;quot;Vblank interrupt&amp;quot;, ROM0[$0040]&lt;/span&gt;
    &lt;span class="c1"&gt;; Fires when the screen finishes drawing the last physical&lt;/span&gt;
    &lt;span class="c1"&gt;; row of pixels&lt;/span&gt;
    &lt;span class="ow"&gt;reti&lt;/span&gt;

&lt;span class="k"&gt;SECTION &amp;quot;LCD controller status interrupt&amp;quot;, ROM0[$0048]&lt;/span&gt;
    &lt;span class="c1"&gt;; Fires on a handful of selectable LCD conditions, e.g.&lt;/span&gt;
    &lt;span class="c1"&gt;; after repainting a specific row on the screen&lt;/span&gt;
    &lt;span class="ow"&gt;reti&lt;/span&gt;

&lt;span class="k"&gt;SECTION &amp;quot;Timer overflow interrupt&amp;quot;, ROM0[$0050]&lt;/span&gt;
    &lt;span class="c1"&gt;; Fires at a configurable fixed interval&lt;/span&gt;
    &lt;span class="ow"&gt;reti&lt;/span&gt;

&lt;span class="k"&gt;SECTION &amp;quot;Serial transfer completion interrupt&amp;quot;, ROM0[$0058]&lt;/span&gt;
    &lt;span class="c1"&gt;; Fires when the serial cable is done?&lt;/span&gt;
    &lt;span class="ow"&gt;reti&lt;/span&gt;

&lt;span class="k"&gt;SECTION &amp;quot;P10-P13 signal low edge interrupt&amp;quot;, ROM0[$0060]&lt;/span&gt;
    &lt;span class="c1"&gt;; Fires when a button is released?&lt;/span&gt;
    &lt;span class="ow"&gt;reti&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;p&gt;These will do nothing.  I mean, obviously, but they&amp;#8217;ll do even &lt;em&gt;less&lt;/em&gt; than nothing until I enable them.  Interrupts are enabled by the dedicated &lt;code&gt;ei&lt;/code&gt; instruction, which enables any interrupts whose corresponding bit is set in the &lt;span class="caps"&gt;IE&lt;/span&gt; register&amp;nbsp;($ffff).&lt;/p&gt;
&lt;p&gt;So…  which one do I&amp;nbsp;want?&lt;/p&gt;
&lt;h2 id="game-loop"&gt;&lt;a class="toclink" href="#game-loop"&gt;Game&amp;nbsp;loop&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;To have a game, I need a game loop.  The basic structure of pretty much any loop looks&amp;nbsp;like:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Load&amp;nbsp;stuff.&lt;/li&gt;
&lt;li&gt;Check for&amp;nbsp;input.&lt;/li&gt;
&lt;li&gt;Update the game&amp;nbsp;state.&lt;/li&gt;
&lt;li&gt;Draw the game&amp;nbsp;state.&lt;/li&gt;
&lt;li&gt;&lt;span class="caps"&gt;GOTO&lt;/span&gt;&amp;nbsp;2&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;(If you&amp;#8217;ve never seen a real game loop written out before, &lt;a href="https://love2d.org/wiki/love.run"&gt;LÖVE&amp;#8217;s default loop&lt;/a&gt; is a good example, though even a huge system like Unity follows &lt;a href="https://docs.unity3d.com/Manual/ExecutionOrder.html"&gt;the same basic structure&lt;/a&gt;.)&lt;/p&gt;
&lt;p&gt;The Game Boy seems to introduce a wrinkle here.  I don&amp;#8217;t actually draw anything myself; rather, the hardware does the drawing, and I tell it &lt;em&gt;what&lt;/em&gt; to draw by using the palette registers, &lt;span class="caps"&gt;OAM&lt;/span&gt;, and &lt;span class="caps"&gt;VRAM&lt;/span&gt;.&lt;/p&gt;
&lt;p&gt;But in fact, this isn&amp;#8217;t too far off from how LÖVE (or Unity) works!  All the drawing I do is applied to a buffer, not the screen; once the drawing is complete, the main loop calls &lt;code&gt;present()&lt;/code&gt;, which &lt;em&gt;waits until vblank&lt;/em&gt; and then draws the buffer to the screen.  So what you see on the screen is delayed by up to a frame, and the loop really has an extra &amp;#8220;wait for vsync&amp;#8221; step at 3½.  Or, with a little&amp;nbsp;rearrangement:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Load&amp;nbsp;stuff.&lt;/li&gt;
&lt;li&gt;Wait for&amp;nbsp;vblank.&lt;/li&gt;
&lt;li&gt;Draw the game&amp;nbsp;state.&lt;/li&gt;
&lt;li&gt;Check for&amp;nbsp;input.&lt;/li&gt;
&lt;li&gt;Update the game&amp;nbsp;state.&lt;/li&gt;
&lt;li&gt;&lt;span class="caps"&gt;GOTO&lt;/span&gt;&amp;nbsp;2&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;This is approaching something I can implement!  It works out especially well because it does all the drawing as early as possible during vblank.  That&amp;#8217;s good, because the &lt;span class="caps"&gt;LCD&lt;/span&gt; operation looks something like&amp;nbsp;this:&lt;/p&gt;
&lt;table class="highlighttable"&gt;&lt;tr&gt;&lt;td class="linenos"&gt;&lt;div class="linenodiv"&gt;&lt;pre&gt;&lt;span class="normal"&gt;1&lt;/span&gt;
&lt;span class="normal"&gt;2&lt;/span&gt;
&lt;span class="normal"&gt;3&lt;/span&gt;
&lt;span class="normal"&gt;4&lt;/span&gt;
&lt;span class="normal"&gt;5&lt;/span&gt;
&lt;span class="normal"&gt;6&lt;/span&gt;
&lt;span class="normal"&gt;7&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/td&gt;&lt;td class="code"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;LCD redrawing...
LCD redrawing...
LCD redrawing...
LCD redrawing...
VBLANK
LCD idle
LCD idle
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;p&gt;While the &lt;span class="caps"&gt;LCD&lt;/span&gt; is refreshing, I can&amp;#8217;t (easily) update anything it might read from.  I only have free control over &lt;span class="caps"&gt;VRAM&lt;/span&gt; et al. during a short interval after vblank, so I need to do all my drawing work &lt;em&gt;right then&lt;/em&gt; to ensure it happens before the &lt;span class="caps"&gt;LCD&lt;/span&gt; starts refreshing again.  Then I&amp;#8217;m free to update the world while the &lt;span class="caps"&gt;LCD&lt;/span&gt; is&amp;nbsp;busy.&lt;/p&gt;
&lt;p&gt;First, right at the entry point, I enable the vblank interrupt.  It&amp;#8217;s bit 0 of the &lt;span class="caps"&gt;IE&lt;/span&gt; register, but &lt;code&gt;hardware.inc&lt;/code&gt; has me&amp;nbsp;covered.&lt;/p&gt;
&lt;table class="highlighttable"&gt;&lt;tr&gt;&lt;td class="linenos"&gt;&lt;div class="linenodiv"&gt;&lt;pre&gt;&lt;span class="normal"&gt;1&lt;/span&gt;
&lt;span class="normal"&gt;2&lt;/span&gt;
&lt;span class="normal"&gt;3&lt;/span&gt;
&lt;span class="normal"&gt;4&lt;/span&gt;
&lt;span class="normal"&gt;5&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/td&gt;&lt;td class="code"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="nf"&gt;main:&lt;/span&gt;
    &lt;span class="c1"&gt;; Enable interrupts&lt;/span&gt;
    &lt;span class="ow"&gt;ld&lt;/span&gt; &lt;span class="nb"&gt;a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;IEF_VBLANK&lt;/span&gt;
    &lt;span class="ow"&gt;ldh&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;rIE&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="nb"&gt;a&lt;/span&gt;
    &lt;span class="ow"&gt;ei&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;p&gt;Next I need to make the handler actually do something.  The obvious approach is for the handler to call one iteration of the game loop, but there are a couple problems with that.  For one, interrupts are disabled when a handler is called, so I would never get any &lt;em&gt;other&lt;/em&gt; interrupts.  I could explicitly re-enable interrupts, but that raises a bigger question: what happens if the game lags, and updating the world takes longer than a frame?  With this approach, the game loop would &lt;em&gt;interrupt itself&lt;/em&gt; and then either return back into itself somewhere and cause untold chaos, or take too long again and eventually overflow the stack.  Neither is&amp;nbsp;appealing.&lt;/p&gt;
&lt;p&gt;An alternative approach, which I found in &lt;a href="https://github.com/exezin/gb-template"&gt;gb-template&lt;/a&gt; but only truly appreciated after some thought, is for the vblank handler to set a flag and immediately return.  The game loop can then &lt;em&gt;wait&lt;/em&gt; until the flag is set before each iteration, just like LÖVE does.  If an update takes longer than a frame, no problem: the loop will always wait until the &lt;em&gt;next&lt;/em&gt; vblank, and the game will simply run more&amp;nbsp;slowly.&lt;/p&gt;
&lt;table class="highlighttable"&gt;&lt;tr&gt;&lt;td class="linenos"&gt;&lt;div class="linenodiv"&gt;&lt;pre&gt;&lt;span class="normal"&gt; 1&lt;/span&gt;
&lt;span class="normal"&gt; 2&lt;/span&gt;
&lt;span class="normal"&gt; 3&lt;/span&gt;
&lt;span class="normal"&gt; 4&lt;/span&gt;
&lt;span class="normal"&gt; 5&lt;/span&gt;
&lt;span class="normal"&gt; 6&lt;/span&gt;
&lt;span class="normal"&gt; 7&lt;/span&gt;
&lt;span class="normal"&gt; 8&lt;/span&gt;
&lt;span class="normal"&gt; 9&lt;/span&gt;
&lt;span class="normal"&gt;10&lt;/span&gt;
&lt;span class="normal"&gt;11&lt;/span&gt;
&lt;span class="normal"&gt;12&lt;/span&gt;
&lt;span class="normal"&gt;13&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/td&gt;&lt;td class="code"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="k"&gt;SECTION &amp;quot;Vblank interrupt&amp;quot;, ROM0[$0040]&lt;/span&gt;
    &lt;span class="ow"&gt;push&lt;/span&gt; &lt;span class="nb"&gt;hl&lt;/span&gt;
    &lt;span class="ow"&gt;ld&lt;/span&gt; &lt;span class="nb"&gt;hl&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;vblank_flag&lt;/span&gt;
    &lt;span class="ow"&gt;ld&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;hl&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="nv"&gt;1&lt;/span&gt;
    &lt;span class="ow"&gt;pop&lt;/span&gt; &lt;span class="nb"&gt;hl&lt;/span&gt;
    &lt;span class="ow"&gt;reti&lt;/span&gt;

&lt;span class="err"&gt;...&lt;/span&gt;

&lt;span class="k"&gt;SECTION &amp;quot;Important twiddles&amp;quot;, WRAM0[$C000]&lt;/span&gt;
&lt;span class="c1"&gt;; Reserve a byte in working RAM to use as the vblank flag&lt;/span&gt;
&lt;span class="nf"&gt;vblank_flag:&lt;/span&gt;
    &lt;span class="k"&gt;db&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;p&gt;The handler fits in eight bytes — the linker would yell at me if it didn&amp;#8217;t, since another section starts at $0048! — and leaves all the registers in their previous states.  As I mentioned before, I originally neglected to preserve registers, and some &lt;em&gt;zany things&lt;/em&gt; started to happen as &lt;code&gt;a&lt;/code&gt; and &lt;code&gt;f&lt;/code&gt; were abruptly altered in the middle of other code.&amp;nbsp;Whoops!&lt;/p&gt;
&lt;p&gt;Now the main loop can look like&amp;nbsp;this:&lt;/p&gt;
&lt;table class="highlighttable"&gt;&lt;tr&gt;&lt;td class="linenos"&gt;&lt;div class="linenodiv"&gt;&lt;pre&gt;&lt;span class="normal"&gt; 1&lt;/span&gt;
&lt;span class="normal"&gt; 2&lt;/span&gt;
&lt;span class="normal"&gt; 3&lt;/span&gt;
&lt;span class="normal"&gt; 4&lt;/span&gt;
&lt;span class="normal"&gt; 5&lt;/span&gt;
&lt;span class="normal"&gt; 6&lt;/span&gt;
&lt;span class="normal"&gt; 7&lt;/span&gt;
&lt;span class="normal"&gt; 8&lt;/span&gt;
&lt;span class="normal"&gt; 9&lt;/span&gt;
&lt;span class="normal"&gt;10&lt;/span&gt;
&lt;span class="normal"&gt;11&lt;/span&gt;
&lt;span class="normal"&gt;12&lt;/span&gt;
&lt;span class="normal"&gt;13&lt;/span&gt;
&lt;span class="normal"&gt;14&lt;/span&gt;
&lt;span class="normal"&gt;15&lt;/span&gt;
&lt;span class="normal"&gt;16&lt;/span&gt;
&lt;span class="normal"&gt;17&lt;/span&gt;
&lt;span class="normal"&gt;18&lt;/span&gt;
&lt;span class="normal"&gt;19&lt;/span&gt;
&lt;span class="normal"&gt;20&lt;/span&gt;
&lt;span class="normal"&gt;21&lt;/span&gt;
&lt;span class="normal"&gt;22&lt;/span&gt;
&lt;span class="normal"&gt;23&lt;/span&gt;
&lt;span class="normal"&gt;24&lt;/span&gt;
&lt;span class="normal"&gt;25&lt;/span&gt;
&lt;span class="normal"&gt;26&lt;/span&gt;
&lt;span class="normal"&gt;27&lt;/span&gt;
&lt;span class="normal"&gt;28&lt;/span&gt;
&lt;span class="normal"&gt;29&lt;/span&gt;
&lt;span class="normal"&gt;30&lt;/span&gt;
&lt;span class="normal"&gt;31&lt;/span&gt;
&lt;span class="normal"&gt;32&lt;/span&gt;
&lt;span class="normal"&gt;33&lt;/span&gt;
&lt;span class="normal"&gt;34&lt;/span&gt;
&lt;span class="normal"&gt;35&lt;/span&gt;
&lt;span class="normal"&gt;36&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/td&gt;&lt;td class="code"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="nf"&gt;main:&lt;/span&gt;
    &lt;span class="c1"&gt;; ... bunch of setup code ...&lt;/span&gt;

&lt;span class="nf"&gt;vblank_loop:&lt;/span&gt;
    &lt;span class="c1"&gt;; Main loop: halt, wait for a vblank, then do stuff&lt;/span&gt;

    &lt;span class="c1"&gt;; The halt instruction stops all CPU activity until the&lt;/span&gt;
    &lt;span class="c1"&gt;; next interrupt, which saves on battery, or at least on&lt;/span&gt;
    &lt;span class="c1"&gt;; CPU cycles on an emulator&amp;#39;s host system.&lt;/span&gt;
    &lt;span class="ow"&gt;halt&lt;/span&gt;
    &lt;span class="c1"&gt;; The Game Boy has some obscure hardware bug where the&lt;/span&gt;
    &lt;span class="c1"&gt;; instruction after a halt is occasionally skipped over,&lt;/span&gt;
    &lt;span class="c1"&gt;; so every halt should be followed by a nop.  This is so&lt;/span&gt;
    &lt;span class="c1"&gt;; ubiquitous that rgbasm automatically adds a nop after&lt;/span&gt;
    &lt;span class="c1"&gt;; every halt, so I don&amp;#39;t even really need this here!&lt;/span&gt;
    &lt;span class="ow"&gt;nop&lt;/span&gt;

    &lt;span class="c1"&gt;; Check to see whether that was a vblank interrupt (since&lt;/span&gt;
    &lt;span class="c1"&gt;; I might later use one of the other interrupts, all of&lt;/span&gt;
    &lt;span class="c1"&gt;; which would also cancel the halt).&lt;/span&gt;
    &lt;span class="ow"&gt;ld&lt;/span&gt; &lt;span class="nb"&gt;a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;vblank_flag&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="c1"&gt;; This sets the zero flag iff a is zero&lt;/span&gt;
    &lt;span class="ow"&gt;and&lt;/span&gt; &lt;span class="nb"&gt;a&lt;/span&gt;
    &lt;span class="ow"&gt;jr&lt;/span&gt; &lt;span class="nv"&gt;z&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;vblank_loop&lt;/span&gt;
    &lt;span class="c1"&gt;; This always sets a to zero, and is shorter (and thus&lt;/span&gt;
    &lt;span class="c1"&gt;; faster) than ld a, 0&lt;/span&gt;
    &lt;span class="ow"&gt;xor&lt;/span&gt; &lt;span class="nb"&gt;a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;a&lt;/span&gt;
    &lt;span class="ow"&gt;ld&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;vblank_flag&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="nb"&gt;a&lt;/span&gt;

    &lt;span class="c1"&gt;; Use DMA to update object attribute memory.&lt;/span&gt;
    &lt;span class="c1"&gt;; Do this FIRST to ensure that it happens before the screen starts to update again.&lt;/span&gt;
    &lt;span class="ow"&gt;call&lt;/span&gt; &lt;span class="mh"&gt;$FF80&lt;/span&gt;

    &lt;span class="c1"&gt;; ... update everything ...&lt;/span&gt;

    &lt;span class="ow"&gt;jp&lt;/span&gt; &lt;span class="nv"&gt;vblank_loop&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;p&gt;It&amp;#8217;s looking all the more convenient that I have my own copy of &lt;span class="caps"&gt;OAM&lt;/span&gt; — I can update it whenever I want during this loop!  I might need similar facilities later on for editing &lt;span class="caps"&gt;VRAM&lt;/span&gt; or changing&amp;nbsp;palettes.&lt;/p&gt;
&lt;h2 id="doing-something-and-reading-input"&gt;&lt;a class="toclink" href="#doing-something-and-reading-input"&gt;Doing something and reading&amp;nbsp;input&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;I have a loop, but since nothing&amp;#8217;s &lt;em&gt;happening&lt;/em&gt;, that&amp;#8217;s not especially obvious.  Input would take a little effort, so I&amp;#8217;ll try something simpler first: making Anise move&amp;nbsp;around.&lt;/p&gt;
&lt;p&gt;I don&amp;#8217;t actually track Anise&amp;#8217;s &lt;em&gt;position&lt;/em&gt; anywhere right now, except for in the &lt;span class="caps"&gt;OAM&lt;/span&gt; buffer.  Good enough.  In my main loop, I&amp;nbsp;add:&lt;/p&gt;
&lt;table class="highlighttable"&gt;&lt;tr&gt;&lt;td class="linenos"&gt;&lt;div class="linenodiv"&gt;&lt;pre&gt;&lt;span class="normal"&gt;1&lt;/span&gt;
&lt;span class="normal"&gt;2&lt;/span&gt;
&lt;span class="normal"&gt;3&lt;/span&gt;
&lt;span class="normal"&gt;4&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/td&gt;&lt;td class="code"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;    &lt;span class="ow"&gt;ld&lt;/span&gt; &lt;span class="nb"&gt;hl&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;oam_buffer&lt;/span&gt; &lt;span class="p"&gt;+&lt;/span&gt; &lt;span class="nv"&gt;1&lt;/span&gt;
    &lt;span class="ow"&gt;ld&lt;/span&gt; &lt;span class="nb"&gt;a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;hl&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="ow"&gt;inc&lt;/span&gt; &lt;span class="nb"&gt;a&lt;/span&gt;
    &lt;span class="ow"&gt;ld&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;hl&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="nb"&gt;a&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;p&gt;The second byte in each &lt;span class="caps"&gt;OAM&lt;/span&gt; entry is the x-coordinate, and indeed, this causes Anise&amp;#8217;s torso to glide rightwards across the screen at 60ish pixels per second.  Eventually the x-coordinate overflows, but that&amp;#8217;s fine; it wraps back to zero and moves the sprite back on-screen from the&amp;nbsp;left.&lt;/p&gt;
&lt;div class="prose-full-illustration"&gt;
&lt;img src="https://eev.ee/media/cheezball/02a-anise-slide.gif" alt="The half-cat is now sliding across the screen"&gt;
&lt;/div&gt;

&lt;p&gt;Excellent.  I mean, sorry, this is &lt;em&gt;extremely&lt;/em&gt; hard to look at, but bear with me a&amp;nbsp;second.&lt;/p&gt;
&lt;p&gt;This would be a &lt;em&gt;bit&lt;/em&gt; more game-like if I could control it with the buttons, so let&amp;#8217;s read from&amp;nbsp;them.&lt;/p&gt;
&lt;p&gt;There are eight buttons: up, down, left, right, A, B, start, select.  There are also eight bits in a byte.  You might suspect that I can simply read an I/O register to get the current state of all eight buttons at&amp;nbsp;once.&lt;/p&gt;
&lt;p&gt;Ha, ha!  You naïve fool.  Of &lt;em&gt;course&lt;/em&gt; it&amp;#8217;s more convoluted than that.  That single byte thing is a pretty good idea, though, so what I&amp;#8217;ll do is read the input at the start of the frame and coax it into a byte that I can consult more easily&amp;nbsp;later.&lt;/p&gt;
&lt;p&gt;Turns out I pretty much &lt;em&gt;have&lt;/em&gt; to do that, because button access is slightly flaky.  Even the official manual advises reading the buttons several times to get a reliable result.&amp;nbsp;Yikes.&lt;/p&gt;
&lt;p&gt;Here&amp;#8217;s how to do it.  The buttons are wired in two groups of four: the dpad and everything else.  Reading them is thus also done in two groups of four.  I need to use the P1 register, which I assume is short for &amp;#8220;player 1&amp;#8221; and is so named because the people who designed this hardware had also designed the two-player &lt;span class="caps"&gt;NES&lt;/span&gt;?&lt;/p&gt;
&lt;p&gt;Bits 5 and 6 of P1 determine which set of four buttons I want to read, and then the lower nybble contains the state of those buttons.  Note that each bit is set to 1 if the button is &lt;em&gt;released&lt;/em&gt;; I think this is a quirk of how they&amp;#8217;re wired, and what I&amp;#8217;m doing is extremely direct hardware access.  Exciting!  (Also very confusing on my first try, where Anise&amp;#8217;s movement was&amp;nbsp;inverted.)&lt;/p&gt;
&lt;p&gt;The code, which is very similar to an example in the official manual, thus looks like&amp;nbsp;this:&lt;/p&gt;
&lt;table class="highlighttable"&gt;&lt;tr&gt;&lt;td class="linenos"&gt;&lt;div class="linenodiv"&gt;&lt;pre&gt;&lt;span class="normal"&gt; 1&lt;/span&gt;
&lt;span class="normal"&gt; 2&lt;/span&gt;
&lt;span class="normal"&gt; 3&lt;/span&gt;
&lt;span class="normal"&gt; 4&lt;/span&gt;
&lt;span class="normal"&gt; 5&lt;/span&gt;
&lt;span class="normal"&gt; 6&lt;/span&gt;
&lt;span class="normal"&gt; 7&lt;/span&gt;
&lt;span class="normal"&gt; 8&lt;/span&gt;
&lt;span class="normal"&gt; 9&lt;/span&gt;
&lt;span class="normal"&gt;10&lt;/span&gt;
&lt;span class="normal"&gt;11&lt;/span&gt;
&lt;span class="normal"&gt;12&lt;/span&gt;
&lt;span class="normal"&gt;13&lt;/span&gt;
&lt;span class="normal"&gt;14&lt;/span&gt;
&lt;span class="normal"&gt;15&lt;/span&gt;
&lt;span class="normal"&gt;16&lt;/span&gt;
&lt;span class="normal"&gt;17&lt;/span&gt;
&lt;span class="normal"&gt;18&lt;/span&gt;
&lt;span class="normal"&gt;19&lt;/span&gt;
&lt;span class="normal"&gt;20&lt;/span&gt;
&lt;span class="normal"&gt;21&lt;/span&gt;
&lt;span class="normal"&gt;22&lt;/span&gt;
&lt;span class="normal"&gt;23&lt;/span&gt;
&lt;span class="normal"&gt;24&lt;/span&gt;
&lt;span class="normal"&gt;25&lt;/span&gt;
&lt;span class="normal"&gt;26&lt;/span&gt;
&lt;span class="normal"&gt;27&lt;/span&gt;
&lt;span class="normal"&gt;28&lt;/span&gt;
&lt;span class="normal"&gt;29&lt;/span&gt;
&lt;span class="normal"&gt;30&lt;/span&gt;
&lt;span class="normal"&gt;31&lt;/span&gt;
&lt;span class="normal"&gt;32&lt;/span&gt;
&lt;span class="normal"&gt;33&lt;/span&gt;
&lt;span class="normal"&gt;34&lt;/span&gt;
&lt;span class="normal"&gt;35&lt;/span&gt;
&lt;span class="normal"&gt;36&lt;/span&gt;
&lt;span class="normal"&gt;37&lt;/span&gt;
&lt;span class="normal"&gt;38&lt;/span&gt;
&lt;span class="normal"&gt;39&lt;/span&gt;
&lt;span class="normal"&gt;40&lt;/span&gt;
&lt;span class="normal"&gt;41&lt;/span&gt;
&lt;span class="normal"&gt;42&lt;/span&gt;
&lt;span class="normal"&gt;43&lt;/span&gt;
&lt;span class="normal"&gt;44&lt;/span&gt;
&lt;span class="normal"&gt;45&lt;/span&gt;
&lt;span class="normal"&gt;46&lt;/span&gt;
&lt;span class="normal"&gt;47&lt;/span&gt;
&lt;span class="normal"&gt;48&lt;/span&gt;
&lt;span class="normal"&gt;49&lt;/span&gt;
&lt;span class="normal"&gt;50&lt;/span&gt;
&lt;span class="normal"&gt;51&lt;/span&gt;
&lt;span class="normal"&gt;52&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/td&gt;&lt;td class="code"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;    &lt;span class="c1"&gt;; Poll input&lt;/span&gt;
    &lt;span class="c1"&gt;; It takes a moment to get a reliable read after requesting&lt;/span&gt;
    &lt;span class="c1"&gt;; a particular set of buttons, so we need to wait a moment;&lt;/span&gt;
    &lt;span class="c1"&gt;; this is based on the code from the manual, which stalls&lt;/span&gt;
    &lt;span class="c1"&gt;; simply by reading multiple times&lt;/span&gt;

    &lt;span class="c1"&gt;; Bit 5 means to read the dpad&lt;/span&gt;
    &lt;span class="c1"&gt;; (Well, Actually: bit 4 being OFF means to read the d-pad)&lt;/span&gt;
    &lt;span class="ow"&gt;ld&lt;/span&gt; &lt;span class="nb"&gt;a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mh"&gt;$20&lt;/span&gt;
    &lt;span class="ow"&gt;ldh&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;rP1&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="nb"&gt;a&lt;/span&gt;
    &lt;span class="c1"&gt;; But it&amp;#39;s unreliable, so do it twice&lt;/span&gt;
    &lt;span class="ow"&gt;ld&lt;/span&gt; &lt;span class="nb"&gt;a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;rP1&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="ow"&gt;ld&lt;/span&gt; &lt;span class="nb"&gt;a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;rP1&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="c1"&gt;; This is &amp;#39;complement&amp;#39;, and flips all the bits in a, so now&lt;/span&gt;
    &lt;span class="c1"&gt;; set bits will mean a button is held down&lt;/span&gt;
    &lt;span class="ow"&gt;cpl&lt;/span&gt;
    &lt;span class="c1"&gt;; Store the lower four bits in b&lt;/span&gt;
    &lt;span class="ow"&gt;and&lt;/span&gt; &lt;span class="nb"&gt;a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mh"&gt;$0f&lt;/span&gt;
    &lt;span class="ow"&gt;ld&lt;/span&gt; &lt;span class="nb"&gt;b&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;a&lt;/span&gt;

    &lt;span class="c1"&gt;; Bit 4 means to read the buttons&lt;/span&gt;
    &lt;span class="c1"&gt;; (Same caveat; it&amp;#39;s really that bit 5 is off)&lt;/span&gt;
    &lt;span class="ow"&gt;ld&lt;/span&gt; &lt;span class="nb"&gt;a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mh"&gt;$10&lt;/span&gt;
    &lt;span class="ow"&gt;ldh&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;rP1&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="nb"&gt;a&lt;/span&gt;
    &lt;span class="c1"&gt;; Not sure why this needs more stalling?  Someone speculated&lt;/span&gt;
    &lt;span class="c1"&gt;; that this circuitry might just be further away&lt;/span&gt;
    &lt;span class="ow"&gt;ld&lt;/span&gt; &lt;span class="nb"&gt;a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;rP1&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="ow"&gt;ld&lt;/span&gt; &lt;span class="nb"&gt;a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;rP1&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="ow"&gt;ld&lt;/span&gt; &lt;span class="nb"&gt;a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;rP1&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="ow"&gt;ld&lt;/span&gt; &lt;span class="nb"&gt;a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;rP1&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="ow"&gt;ld&lt;/span&gt; &lt;span class="nb"&gt;a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;rP1&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="ow"&gt;ld&lt;/span&gt; &lt;span class="nb"&gt;a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;rP1&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="c1"&gt;; Again, complement and mask off the lower four bits&lt;/span&gt;
    &lt;span class="ow"&gt;cpl&lt;/span&gt;
    &lt;span class="ow"&gt;and&lt;/span&gt; &lt;span class="nb"&gt;a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mh"&gt;$0f&lt;/span&gt;
    &lt;span class="c1"&gt;; b already contains four bits, so I need to shift something&lt;/span&gt;
    &lt;span class="c1"&gt;; left by four...  but the shift instructions only go one&lt;/span&gt;
    &lt;span class="c1"&gt;; bit at a time, ugh!  Luckily there&amp;#39;s swap, which swaps the&lt;/span&gt;
    &lt;span class="c1"&gt;; high and low nybbles in any register&lt;/span&gt;
    &lt;span class="ow"&gt;swap&lt;/span&gt; &lt;span class="nb"&gt;a&lt;/span&gt;
    &lt;span class="c1"&gt;; Combine b&amp;#39;s lower nybble with a&amp;#39;s high nybble&lt;/span&gt;
    &lt;span class="ow"&gt;or&lt;/span&gt; &lt;span class="nb"&gt;a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;b&lt;/span&gt;
    &lt;span class="c1"&gt;; And finally store it in RAM&lt;/span&gt;
    &lt;span class="ow"&gt;ld&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;buttons&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="nb"&gt;a&lt;/span&gt;

&lt;span class="err"&gt;...&lt;/span&gt;

&lt;span class="k"&gt;SECTION &amp;quot;Important twiddles&amp;quot;, WRAM0[$C000]&lt;/span&gt;
&lt;span class="nf"&gt;vblank_flag:&lt;/span&gt;
    &lt;span class="k"&gt;db&lt;/span&gt;
&lt;span class="nf"&gt;buttons:&lt;/span&gt;
    &lt;span class="k"&gt;db&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;p&gt;Phew.  That was a bit of a journey, but now I have the button state as a single byte.  To help with reading the buttons, I&amp;#8217;ll also define a few constants labeling the individual bits.  (There are instructions for reading a particular bit by number, so I don&amp;#8217;t need to mask a single bit&amp;nbsp;out.)&lt;/p&gt;
&lt;table class="highlighttable"&gt;&lt;tr&gt;&lt;td class="linenos"&gt;&lt;div class="linenodiv"&gt;&lt;pre&gt;&lt;span class="normal"&gt;1&lt;/span&gt;
&lt;span class="normal"&gt;2&lt;/span&gt;
&lt;span class="normal"&gt;3&lt;/span&gt;
&lt;span class="normal"&gt;4&lt;/span&gt;
&lt;span class="normal"&gt;5&lt;/span&gt;
&lt;span class="normal"&gt;6&lt;/span&gt;
&lt;span class="normal"&gt;7&lt;/span&gt;
&lt;span class="normal"&gt;8&lt;/span&gt;
&lt;span class="normal"&gt;9&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/td&gt;&lt;td class="code"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="c1"&gt;; Constants&lt;/span&gt;
&lt;span class="nf"&gt;BUTTON_RIGHT&lt;/span&gt;  &lt;span class="k"&gt;EQU&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;
&lt;span class="nf"&gt;BUTTON_LEFT&lt;/span&gt;   &lt;span class="k"&gt;EQU&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
&lt;span class="nf"&gt;BUTTON_UP&lt;/span&gt;     &lt;span class="k"&gt;EQU&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;
&lt;span class="nf"&gt;BUTTON_DOWN&lt;/span&gt;   &lt;span class="k"&gt;EQU&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;
&lt;span class="nf"&gt;BUTTON_A&lt;/span&gt;      &lt;span class="k"&gt;EQU&lt;/span&gt; &lt;span class="mi"&gt;4&lt;/span&gt;
&lt;span class="nf"&gt;BUTTON_B&lt;/span&gt;      &lt;span class="k"&gt;EQU&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt;
&lt;span class="nf"&gt;BUTTON_START&lt;/span&gt;  &lt;span class="k"&gt;EQU&lt;/span&gt; &lt;span class="mi"&gt;6&lt;/span&gt;
&lt;span class="nf"&gt;BUTTON_SELECT&lt;/span&gt; &lt;span class="k"&gt;EQU&lt;/span&gt; &lt;span class="mi"&gt;7&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;p&gt;Now to adjust the sprite position based on what directions are held down.  Delete the old code and replace it&amp;nbsp;with:&lt;/p&gt;
&lt;table class="highlighttable"&gt;&lt;tr&gt;&lt;td class="linenos"&gt;&lt;div class="linenodiv"&gt;&lt;pre&gt;&lt;span class="normal"&gt; 1&lt;/span&gt;
&lt;span class="normal"&gt; 2&lt;/span&gt;
&lt;span class="normal"&gt; 3&lt;/span&gt;
&lt;span class="normal"&gt; 4&lt;/span&gt;
&lt;span class="normal"&gt; 5&lt;/span&gt;
&lt;span class="normal"&gt; 6&lt;/span&gt;
&lt;span class="normal"&gt; 7&lt;/span&gt;
&lt;span class="normal"&gt; 8&lt;/span&gt;
&lt;span class="normal"&gt; 9&lt;/span&gt;
&lt;span class="normal"&gt;10&lt;/span&gt;
&lt;span class="normal"&gt;11&lt;/span&gt;
&lt;span class="normal"&gt;12&lt;/span&gt;
&lt;span class="normal"&gt;13&lt;/span&gt;
&lt;span class="normal"&gt;14&lt;/span&gt;
&lt;span class="normal"&gt;15&lt;/span&gt;
&lt;span class="normal"&gt;16&lt;/span&gt;
&lt;span class="normal"&gt;17&lt;/span&gt;
&lt;span class="normal"&gt;18&lt;/span&gt;
&lt;span class="normal"&gt;19&lt;/span&gt;
&lt;span class="normal"&gt;20&lt;/span&gt;
&lt;span class="normal"&gt;21&lt;/span&gt;
&lt;span class="normal"&gt;22&lt;/span&gt;
&lt;span class="normal"&gt;23&lt;/span&gt;
&lt;span class="normal"&gt;24&lt;/span&gt;
&lt;span class="normal"&gt;25&lt;/span&gt;
&lt;span class="normal"&gt;26&lt;/span&gt;
&lt;span class="normal"&gt;27&lt;/span&gt;
&lt;span class="normal"&gt;28&lt;/span&gt;
&lt;span class="normal"&gt;29&lt;/span&gt;
&lt;span class="normal"&gt;30&lt;/span&gt;
&lt;span class="normal"&gt;31&lt;/span&gt;
&lt;span class="normal"&gt;32&lt;/span&gt;
&lt;span class="normal"&gt;33&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/td&gt;&lt;td class="code"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;    &lt;span class="c1"&gt;; Set b/c to the y/x coordinates&lt;/span&gt;
    &lt;span class="ow"&gt;ld&lt;/span&gt; &lt;span class="nb"&gt;hl&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;oam_buffer&lt;/span&gt;
    &lt;span class="ow"&gt;ld&lt;/span&gt; &lt;span class="nb"&gt;b&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;hl&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="ow"&gt;inc&lt;/span&gt; &lt;span class="nb"&gt;hl&lt;/span&gt;
    &lt;span class="ow"&gt;ld&lt;/span&gt; &lt;span class="nb"&gt;c&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;hl&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;

    &lt;span class="c1"&gt;; This sets the z flag to match a particular bit in a&lt;/span&gt;
    &lt;span class="ow"&gt;bit&lt;/span&gt; &lt;span class="nv"&gt;BUTTON_LEFT&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;a&lt;/span&gt;
    &lt;span class="c1"&gt;; If z, the bit is zero, so left isn&amp;#39;t held down&lt;/span&gt;
    &lt;span class="ow"&gt;jr&lt;/span&gt; &lt;span class="nv"&gt;z&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="err"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;skip_left&lt;/span&gt;
    &lt;span class="c1"&gt;; Otherwise, left is held down, so decrement x&lt;/span&gt;
    &lt;span class="ow"&gt;dec&lt;/span&gt; &lt;span class="nb"&gt;c&lt;/span&gt;
&lt;span class="nf"&gt;.skip_left:&lt;/span&gt;

    &lt;span class="c1"&gt;; The other three directions work the same way&lt;/span&gt;
    &lt;span class="ow"&gt;bit&lt;/span&gt; &lt;span class="nv"&gt;BUTTON_RIGHT&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;a&lt;/span&gt;
    &lt;span class="ow"&gt;jr&lt;/span&gt; &lt;span class="nv"&gt;z&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="err"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;skip_right&lt;/span&gt;
    &lt;span class="ow"&gt;inc&lt;/span&gt; &lt;span class="nb"&gt;c&lt;/span&gt;
&lt;span class="nf"&gt;.skip_right:&lt;/span&gt;
    &lt;span class="ow"&gt;bit&lt;/span&gt; &lt;span class="nv"&gt;BUTTON_UP&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;a&lt;/span&gt;
    &lt;span class="ow"&gt;jr&lt;/span&gt; &lt;span class="nv"&gt;z&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="err"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;skip_up&lt;/span&gt;
    &lt;span class="ow"&gt;dec&lt;/span&gt; &lt;span class="nb"&gt;b&lt;/span&gt;
&lt;span class="nf"&gt;.skip_up:&lt;/span&gt;
    &lt;span class="ow"&gt;bit&lt;/span&gt; &lt;span class="nv"&gt;BUTTON_DOWN&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;a&lt;/span&gt;
    &lt;span class="ow"&gt;jr&lt;/span&gt; &lt;span class="nv"&gt;z&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="err"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;skip_down&lt;/span&gt;
    &lt;span class="ow"&gt;inc&lt;/span&gt; &lt;span class="nb"&gt;b&lt;/span&gt;
&lt;span class="nf"&gt;.skip_down:&lt;/span&gt;

    &lt;span class="c1"&gt;; Finally, write the new coordinates back to the OAM&lt;/span&gt;
    &lt;span class="c1"&gt;; buffer, which hl is still pointing into&lt;/span&gt;
    &lt;span class="ow"&gt;ld&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;hl&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="nb"&gt;c&lt;/span&gt;
    &lt;span class="ow"&gt;dec&lt;/span&gt; &lt;span class="nb"&gt;hl&lt;/span&gt;
    &lt;span class="ow"&gt;ld&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;hl&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="nb"&gt;b&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;p&gt;Miraculously, Anise&amp;#8217;s torso now moves around on&amp;nbsp;command!&lt;/p&gt;
&lt;div class="prose-full-illustration"&gt;
&lt;img src="https://eev.ee/media/cheezball/02b-anise-slide-dpad.gif" alt="The half-cat is now moving according to button presses"&gt;
&lt;/div&gt;

&lt;p&gt;Neat!  But this still looks really, really, incredibly&amp;nbsp;bad.&lt;/p&gt;
&lt;h2 id="aesthetics"&gt;&lt;a class="toclink" href="#aesthetics"&gt;Aesthetics&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;It&amp;#8217;s time to do something about this&amp;nbsp;artwork.&lt;/p&gt;
&lt;p&gt;First things first: I&amp;#8217;m really tired of writing out colors &lt;em&gt;by hand&lt;/em&gt;, &lt;em&gt;in binary&lt;/em&gt;, so let&amp;#8217;s fix that.  In reality, I did this bit &lt;em&gt;after&lt;/em&gt; adding better art, but doing it first is better for&amp;nbsp;everyone.&lt;/p&gt;
&lt;p&gt;I think I&amp;#8217;ve mentioned before that rgbasm has (very, very rudimentary) support for macros, and this seems like a perfect use case for one.  I&amp;#8217;d like to be able to write colors out in typical &lt;code&gt;rrggbb&lt;/code&gt; hex fashion, so I need to convert a 24-bit color to a 16-bit&amp;nbsp;one.&lt;/p&gt;
&lt;table class="highlighttable"&gt;&lt;tr&gt;&lt;td class="linenos"&gt;&lt;div class="linenodiv"&gt;&lt;pre&gt;&lt;span class="normal"&gt;1&lt;/span&gt;
&lt;span class="normal"&gt;2&lt;/span&gt;
&lt;span class="normal"&gt;3&lt;/span&gt;
&lt;span class="normal"&gt;4&lt;/span&gt;
&lt;span class="normal"&gt;5&lt;/span&gt;
&lt;span class="normal"&gt;6&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/td&gt;&lt;td class="code"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="nf"&gt;dcolor:&lt;/span&gt; &lt;span class="k"&gt;MACRO  ; $rrggbb -&amp;gt; gbc representation&lt;/span&gt;
&lt;span class="err"&gt;_r&lt;/span&gt; &lt;span class="k"&gt;=&lt;/span&gt; &lt;span class="err"&gt;((\&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="err"&gt;)&lt;/span&gt; &lt;span class="err"&gt;&amp;amp;&lt;/span&gt; &lt;span class="mh"&gt;$ff0000&lt;/span&gt;&lt;span class="err"&gt;)&lt;/span&gt; &lt;span class="err"&gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;16&lt;/span&gt; &lt;span class="err"&gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;
&lt;span class="nf"&gt;_g&lt;/span&gt; &lt;span class="k"&gt;=&lt;/span&gt; &lt;span class="err"&gt;((\&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="err"&gt;)&lt;/span&gt; &lt;span class="err"&gt;&amp;amp;&lt;/span&gt; &lt;span class="mh"&gt;$00ff00&lt;/span&gt;&lt;span class="err"&gt;)&lt;/span&gt; &lt;span class="err"&gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;8&lt;/span&gt;  &lt;span class="err"&gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;
&lt;span class="nf"&gt;_b&lt;/span&gt; &lt;span class="k"&gt;=&lt;/span&gt; &lt;span class="err"&gt;((\&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="err"&gt;)&lt;/span&gt; &lt;span class="err"&gt;&amp;amp;&lt;/span&gt; &lt;span class="mh"&gt;$0000ff&lt;/span&gt;&lt;span class="err"&gt;)&lt;/span&gt; &lt;span class="err"&gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;  &lt;span class="err"&gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;
    &lt;span class="k"&gt;dw (_r &amp;lt;&amp;lt; 0) | (_g &amp;lt;&amp;lt; 5) | (_b &amp;lt;&amp;lt; 10)&lt;/span&gt;
    &lt;span class="k"&gt;ENDM&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;p&gt;This is going to need a whole &lt;em&gt;paragraph&lt;/em&gt; of&amp;nbsp;caveats.&lt;/p&gt;
&lt;p&gt;A macro is contained between &lt;code&gt;MACRO&lt;/code&gt; and &lt;code&gt;ENDM&lt;/code&gt;.  The assembler has a curious sort of universal assignment syntax, where even ephemeral constructs like macros are introduced by labels.  Macros can take arguments, but they aren&amp;#8217;t declared; they&amp;#8217;re passed more like arguments to shell scripts, where the first argument is &lt;code&gt;\1&lt;/code&gt; and so forth.  (There&amp;#8217;s even a &lt;code&gt;SHIFT&lt;/code&gt; command for accessing arguments beyond the ninth.)  Also, passing strings to a macro is some kind of byzantine nightmare where you have to slap backslashes in just the right places and I will probably avoid doing it altogether if I can at all help&amp;nbsp;it.&lt;/p&gt;
&lt;p&gt;Oh, one other caveat: compile-time assignments like I have above &lt;em&gt;must start in the first column&lt;/em&gt;.  I believe this is because assignments are &lt;em&gt;also&lt;/em&gt; labels, and labels have to start in the first column.  It&amp;#8217;s a bit weird and apparently rgbasm&amp;#8217;s lexer is horrifying, but I&amp;#8217;ll take it over writing my own assembler and stretching this project out any&amp;nbsp;further.&lt;/p&gt;
&lt;p&gt;Anyway, all of that lets me write &lt;code&gt;dcolor $ff0044&lt;/code&gt; somewhere and have it translated at compile time to the appropriate 16-bit value.  (I used &lt;code&gt;dcolor&lt;/code&gt; to parallel &lt;code&gt;db&lt;/code&gt; and friends, but I&amp;#8217;m strongly considering using CamelCase exclusively for macros?  Guess it depends how heavily I use&amp;nbsp;them.)&lt;/p&gt;
&lt;p&gt;With that on hand, I can now doodle some little sprites in &lt;a href="https://www.aseprite.org/"&gt;Aseprite&lt;/a&gt; and copy them in.  This part is not especially interesting and involves a lot of squinting at zoomed-in&amp;nbsp;sprites.&lt;/p&gt;
&lt;table class="highlighttable"&gt;&lt;tr&gt;&lt;td class="linenos"&gt;&lt;div class="linenodiv"&gt;&lt;pre&gt;&lt;span class="normal"&gt; 1&lt;/span&gt;
&lt;span class="normal"&gt; 2&lt;/span&gt;
&lt;span class="normal"&gt; 3&lt;/span&gt;
&lt;span class="normal"&gt; 4&lt;/span&gt;
&lt;span class="normal"&gt; 5&lt;/span&gt;
&lt;span class="normal"&gt; 6&lt;/span&gt;
&lt;span class="normal"&gt; 7&lt;/span&gt;
&lt;span class="normal"&gt; 8&lt;/span&gt;
&lt;span class="normal"&gt; 9&lt;/span&gt;
&lt;span class="normal"&gt;10&lt;/span&gt;
&lt;span class="normal"&gt;11&lt;/span&gt;
&lt;span class="normal"&gt;12&lt;/span&gt;
&lt;span class="normal"&gt;13&lt;/span&gt;
&lt;span class="normal"&gt;14&lt;/span&gt;
&lt;span class="normal"&gt;15&lt;/span&gt;
&lt;span class="normal"&gt;16&lt;/span&gt;
&lt;span class="normal"&gt;17&lt;/span&gt;
&lt;span class="normal"&gt;18&lt;/span&gt;
&lt;span class="normal"&gt;19&lt;/span&gt;
&lt;span class="normal"&gt;20&lt;/span&gt;
&lt;span class="normal"&gt;21&lt;/span&gt;
&lt;span class="normal"&gt;22&lt;/span&gt;
&lt;span class="normal"&gt;23&lt;/span&gt;
&lt;span class="normal"&gt;24&lt;/span&gt;
&lt;span class="normal"&gt;25&lt;/span&gt;
&lt;span class="normal"&gt;26&lt;/span&gt;
&lt;span class="normal"&gt;27&lt;/span&gt;
&lt;span class="normal"&gt;28&lt;/span&gt;
&lt;span class="normal"&gt;29&lt;/span&gt;
&lt;span class="normal"&gt;30&lt;/span&gt;
&lt;span class="normal"&gt;31&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/td&gt;&lt;td class="code"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="k"&gt;SECTION &amp;quot;Sprites&amp;quot;, ROM0&lt;/span&gt;
&lt;span class="nf"&gt;PALETTE_BG0:&lt;/span&gt;
    &lt;span class="err"&gt;dcolor $80c870  ; light green&lt;/span&gt;
    &lt;span class="err"&gt;dcolor $48b038  ; darker green&lt;/span&gt;
    &lt;span class="err"&gt;dcolor $000000  ; unused&lt;/span&gt;
    &lt;span class="err"&gt;dcolor $000000  ; unused&lt;/span&gt;
&lt;span class="nf"&gt;PALETTE_ANISE:&lt;/span&gt;
    &lt;span class="err"&gt;dcolor $000000  ; TODO&lt;/span&gt;
    &lt;span class="err"&gt;dcolor $204048&lt;/span&gt;
    &lt;span class="err"&gt;dcolor $20b0b0&lt;/span&gt;
    &lt;span class="err"&gt;dcolor $f8f8f8&lt;/span&gt;
&lt;span class="nf"&gt;GRASS_SPRITE:&lt;/span&gt;
    &lt;span class="k"&gt;dw `00000000&lt;/span&gt;
    &lt;span class="k"&gt;dw `00000000&lt;/span&gt;
    &lt;span class="k"&gt;dw `01000100&lt;/span&gt;
    &lt;span class="k"&gt;dw `01010100&lt;/span&gt;
    &lt;span class="k"&gt;dw `00010000&lt;/span&gt;
    &lt;span class="k"&gt;dw `00000000&lt;/span&gt;
    &lt;span class="k"&gt;dw `00000000&lt;/span&gt;
    &lt;span class="k"&gt;dw `00000000&lt;/span&gt;
&lt;span class="nf"&gt;EMPTY_SPRITE:&lt;/span&gt;
    &lt;span class="k"&gt;dw `00000000&lt;/span&gt;
    &lt;span class="k"&gt;dw `00000000&lt;/span&gt;
    &lt;span class="k"&gt;dw `00000000&lt;/span&gt;
    &lt;span class="k"&gt;dw `00000000&lt;/span&gt;
    &lt;span class="k"&gt;dw `00000000&lt;/span&gt;
    &lt;span class="k"&gt;dw `00000000&lt;/span&gt;
    &lt;span class="k"&gt;dw `00000000&lt;/span&gt;
    &lt;span class="k"&gt;dw `00000000&lt;/span&gt;
&lt;span class="nf"&gt;ANISE_SPRITE:&lt;/span&gt;
    &lt;span class="c1"&gt;; ... I&amp;#39;ll revisit this momentarily&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;p&gt;Gorgeous.  You may notice that I put the colors as &lt;em&gt;data&lt;/em&gt; instead of inlining them in code, which incidentally makes the code for setting the palette vastly shorter as&amp;nbsp;well:&lt;/p&gt;
&lt;table class="highlighttable"&gt;&lt;tr&gt;&lt;td class="linenos"&gt;&lt;div class="linenodiv"&gt;&lt;pre&gt;&lt;span class="normal"&gt; 1&lt;/span&gt;
&lt;span class="normal"&gt; 2&lt;/span&gt;
&lt;span class="normal"&gt; 3&lt;/span&gt;
&lt;span class="normal"&gt; 4&lt;/span&gt;
&lt;span class="normal"&gt; 5&lt;/span&gt;
&lt;span class="normal"&gt; 6&lt;/span&gt;
&lt;span class="normal"&gt; 7&lt;/span&gt;
&lt;span class="normal"&gt; 8&lt;/span&gt;
&lt;span class="normal"&gt; 9&lt;/span&gt;
&lt;span class="normal"&gt;10&lt;/span&gt;
&lt;span class="normal"&gt;11&lt;/span&gt;
&lt;span class="normal"&gt;12&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/td&gt;&lt;td class="code"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;    &lt;span class="c1"&gt;; Start setting the first color, and advance the internal&lt;/span&gt;
    &lt;span class="c1"&gt;; pointer on every write&lt;/span&gt;
    &lt;span class="ow"&gt;ld&lt;/span&gt; &lt;span class="nb"&gt;a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mb"&gt;%10000000&lt;/span&gt;
    &lt;span class="c1"&gt;; BCPS = Background Color Palette Specification&lt;/span&gt;
    &lt;span class="ow"&gt;ldh&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;rBCPS&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="nb"&gt;a&lt;/span&gt;

    &lt;span class="ow"&gt;ld&lt;/span&gt; &lt;span class="nb"&gt;hl&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;PALETTE_BG0&lt;/span&gt;
    &lt;span class="k"&gt;REPT 8&lt;/span&gt;
    &lt;span class="ow"&gt;ld&lt;/span&gt; &lt;span class="nb"&gt;a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;hl&lt;/span&gt;&lt;span class="p"&gt;+]&lt;/span&gt;
    &lt;span class="c1"&gt;; Same, but Data&lt;/span&gt;
    &lt;span class="ow"&gt;ld&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;rBCPD&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="nb"&gt;a&lt;/span&gt;
    &lt;span class="k"&gt;ENDR&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;p&gt;Loading sprites into &lt;span class="caps"&gt;VRAM&lt;/span&gt; also becomes a bit less of a&amp;nbsp;mess:&lt;/p&gt;
&lt;table class="highlighttable"&gt;&lt;tr&gt;&lt;td class="linenos"&gt;&lt;div class="linenodiv"&gt;&lt;pre&gt;&lt;span class="normal"&gt; 1&lt;/span&gt;
&lt;span class="normal"&gt; 2&lt;/span&gt;
&lt;span class="normal"&gt; 3&lt;/span&gt;
&lt;span class="normal"&gt; 4&lt;/span&gt;
&lt;span class="normal"&gt; 5&lt;/span&gt;
&lt;span class="normal"&gt; 6&lt;/span&gt;
&lt;span class="normal"&gt; 7&lt;/span&gt;
&lt;span class="normal"&gt; 8&lt;/span&gt;
&lt;span class="normal"&gt; 9&lt;/span&gt;
&lt;span class="normal"&gt;10&lt;/span&gt;
&lt;span class="normal"&gt;11&lt;/span&gt;
&lt;span class="normal"&gt;12&lt;/span&gt;
&lt;span class="normal"&gt;13&lt;/span&gt;
&lt;span class="normal"&gt;14&lt;/span&gt;
&lt;span class="normal"&gt;15&lt;/span&gt;
&lt;span class="normal"&gt;16&lt;/span&gt;
&lt;span class="normal"&gt;17&lt;/span&gt;
&lt;span class="normal"&gt;18&lt;/span&gt;
&lt;span class="normal"&gt;19&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/td&gt;&lt;td class="code"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;    &lt;span class="c1"&gt;; Load some basic tiles&lt;/span&gt;
    &lt;span class="ow"&gt;ld&lt;/span&gt; &lt;span class="nb"&gt;hl&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mh"&gt;$8000&lt;/span&gt;

    &lt;span class="c1"&gt;; Read the 16-byte empty sprite into tile 0&lt;/span&gt;
    &lt;span class="ow"&gt;ld&lt;/span&gt; &lt;span class="nb"&gt;bc&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;EMPTY_SPRITE&lt;/span&gt;
    &lt;span class="k"&gt;REPT 16&lt;/span&gt;
    &lt;span class="ow"&gt;ld&lt;/span&gt; &lt;span class="nb"&gt;a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;bc&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="ow"&gt;inc&lt;/span&gt; &lt;span class="nb"&gt;bc&lt;/span&gt;
    &lt;span class="ow"&gt;ld&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;hl&lt;/span&gt;&lt;span class="p"&gt;+],&lt;/span&gt; &lt;span class="nb"&gt;a&lt;/span&gt;
    &lt;span class="k"&gt;ENDR&lt;/span&gt;

    &lt;span class="c1"&gt;; Read the grass sprite into tile 1, which immediately&lt;/span&gt;
    &lt;span class="c1"&gt;; follows tile 0, so hl is already in the right place&lt;/span&gt;
    &lt;span class="ow"&gt;ld&lt;/span&gt; &lt;span class="nb"&gt;bc&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;GRASS_SPRITE&lt;/span&gt;
    &lt;span class="k"&gt;REPT 16&lt;/span&gt;
    &lt;span class="ow"&gt;ld&lt;/span&gt; &lt;span class="nb"&gt;a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;bc&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="ow"&gt;inc&lt;/span&gt; &lt;span class="nb"&gt;bc&lt;/span&gt;
    &lt;span class="ow"&gt;ld&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;hl&lt;/span&gt;&lt;span class="p"&gt;+],&lt;/span&gt; &lt;span class="nb"&gt;a&lt;/span&gt;
    &lt;span class="k"&gt;ENDR&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;p&gt;Someday I should write an actual copy function, since at the moment, I&amp;#8217;m using an alarming amount of space for pointlessly unrolled loops.  Maybe&amp;nbsp;later.&lt;/p&gt;
&lt;p&gt;You may notice I now have &lt;em&gt;two&lt;/em&gt; tiles, whereas before I was relying on filling the entire screen with &lt;em&gt;one&lt;/em&gt; tile, tile 0.  I want to dot the landscape with tile 1, which means writing a bit more to the actual background grid, which begins at $9800 and has one byte per&amp;nbsp;tile.&lt;/p&gt;
&lt;table class="highlighttable"&gt;&lt;tr&gt;&lt;td class="linenos"&gt;&lt;div class="linenodiv"&gt;&lt;pre&gt;&lt;span class="normal"&gt; 1&lt;/span&gt;
&lt;span class="normal"&gt; 2&lt;/span&gt;
&lt;span class="normal"&gt; 3&lt;/span&gt;
&lt;span class="normal"&gt; 4&lt;/span&gt;
&lt;span class="normal"&gt; 5&lt;/span&gt;
&lt;span class="normal"&gt; 6&lt;/span&gt;
&lt;span class="normal"&gt; 7&lt;/span&gt;
&lt;span class="normal"&gt; 8&lt;/span&gt;
&lt;span class="normal"&gt; 9&lt;/span&gt;
&lt;span class="normal"&gt;10&lt;/span&gt;
&lt;span class="normal"&gt;11&lt;/span&gt;
&lt;span class="normal"&gt;12&lt;/span&gt;
&lt;span class="normal"&gt;13&lt;/span&gt;
&lt;span class="normal"&gt;14&lt;/span&gt;
&lt;span class="normal"&gt;15&lt;/span&gt;
&lt;span class="normal"&gt;16&lt;/span&gt;
&lt;span class="normal"&gt;17&lt;/span&gt;
&lt;span class="normal"&gt;18&lt;/span&gt;
&lt;span class="normal"&gt;19&lt;/span&gt;
&lt;span class="normal"&gt;20&lt;/span&gt;
&lt;span class="normal"&gt;21&lt;/span&gt;
&lt;span class="normal"&gt;22&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/td&gt;&lt;td class="code"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;    &lt;span class="c1"&gt;; Fill the screen buffer with a pattern of grass tiles,&lt;/span&gt;
    &lt;span class="c1"&gt;; where every 2x2 block has a single grass at the top left.&lt;/span&gt;
    &lt;span class="c1"&gt;; Note that the buffer is 32x32 tiles, and it ends at $9c00&lt;/span&gt;
    &lt;span class="ow"&gt;ld&lt;/span&gt; &lt;span class="nb"&gt;hl&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mh"&gt;$9800&lt;/span&gt;
&lt;span class="nf"&gt;.screen_fill_loop:&lt;/span&gt;
    &lt;span class="c1"&gt;; Use tile 1 for every other tile in this row.  Note that&lt;/span&gt;
    &lt;span class="c1"&gt;; REPTed part increments hl /twice/, thus skipping a tile&lt;/span&gt;
    &lt;span class="ow"&gt;ld&lt;/span&gt; &lt;span class="nb"&gt;a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mh"&gt;$01&lt;/span&gt;
    &lt;span class="k"&gt;REPT 16&lt;/span&gt;
    &lt;span class="ow"&gt;ld&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;hl&lt;/span&gt;&lt;span class="p"&gt;+],&lt;/span&gt; &lt;span class="nb"&gt;a&lt;/span&gt;
    &lt;span class="ow"&gt;inc&lt;/span&gt; &lt;span class="nb"&gt;hl&lt;/span&gt;
    &lt;span class="k"&gt;ENDR&lt;/span&gt;
    &lt;span class="c1"&gt;; Skip an entire row of 32 tiles, which will remain empty.&lt;/span&gt;
    &lt;span class="c1"&gt;; There is almost certainly a better way to do this, but I&lt;/span&gt;
    &lt;span class="c1"&gt;; didn&amp;#39;t do it.  (Hint: it&amp;#39;s ld bc, $20; add hl, bc)&lt;/span&gt;
    &lt;span class="k"&gt;REPT 32&lt;/span&gt;
    &lt;span class="ow"&gt;inc&lt;/span&gt; &lt;span class="nb"&gt;hl&lt;/span&gt;
    &lt;span class="k"&gt;ENDR&lt;/span&gt;
    &lt;span class="c1"&gt;; If we haven&amp;#39;t reached $9c00 yet, continue looping&lt;/span&gt;
    &lt;span class="ow"&gt;ld&lt;/span&gt; &lt;span class="nb"&gt;a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;h&lt;/span&gt;
    &lt;span class="ow"&gt;cp&lt;/span&gt; &lt;span class="nb"&gt;a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mh"&gt;$9C&lt;/span&gt;
    &lt;span class="ow"&gt;jr&lt;/span&gt; &lt;span class="nb"&gt;c&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="err"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;screen_fill_loop&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;p&gt;Sorry for all these big blocks of code, but &lt;em&gt;check out this payoff&lt;/em&gt;!&lt;/p&gt;
&lt;div class="prose-full-illustration"&gt;
&lt;img src="https://eev.ee/media/cheezball/02c-grass-background.png" alt="A very simple grassy background"&gt;
&lt;/div&gt;

&lt;p&gt;&lt;span class="caps"&gt;POW&lt;/span&gt;!&amp;nbsp;Gorgeous.&lt;/p&gt;
&lt;p&gt;And hey, why stop there?  With a little more pixel arting against a very reduced&amp;nbsp;palette&amp;#8230;&lt;/p&gt;
&lt;table class="highlighttable"&gt;&lt;tr&gt;&lt;td class="linenos"&gt;&lt;div class="linenodiv"&gt;&lt;pre&gt;&lt;span class="normal"&gt; 1&lt;/span&gt;
&lt;span class="normal"&gt; 2&lt;/span&gt;
&lt;span class="normal"&gt; 3&lt;/span&gt;
&lt;span class="normal"&gt; 4&lt;/span&gt;
&lt;span class="normal"&gt; 5&lt;/span&gt;
&lt;span class="normal"&gt; 6&lt;/span&gt;
&lt;span class="normal"&gt; 7&lt;/span&gt;
&lt;span class="normal"&gt; 8&lt;/span&gt;
&lt;span class="normal"&gt; 9&lt;/span&gt;
&lt;span class="normal"&gt;10&lt;/span&gt;
&lt;span class="normal"&gt;11&lt;/span&gt;
&lt;span class="normal"&gt;12&lt;/span&gt;
&lt;span class="normal"&gt;13&lt;/span&gt;
&lt;span class="normal"&gt;14&lt;/span&gt;
&lt;span class="normal"&gt;15&lt;/span&gt;
&lt;span class="normal"&gt;16&lt;/span&gt;
&lt;span class="normal"&gt;17&lt;/span&gt;
&lt;span class="normal"&gt;18&lt;/span&gt;
&lt;span class="normal"&gt;19&lt;/span&gt;
&lt;span class="normal"&gt;20&lt;/span&gt;
&lt;span class="normal"&gt;21&lt;/span&gt;
&lt;span class="normal"&gt;22&lt;/span&gt;
&lt;span class="normal"&gt;23&lt;/span&gt;
&lt;span class="normal"&gt;24&lt;/span&gt;
&lt;span class="normal"&gt;25&lt;/span&gt;
&lt;span class="normal"&gt;26&lt;/span&gt;
&lt;span class="normal"&gt;27&lt;/span&gt;
&lt;span class="normal"&gt;28&lt;/span&gt;
&lt;span class="normal"&gt;29&lt;/span&gt;
&lt;span class="normal"&gt;30&lt;/span&gt;
&lt;span class="normal"&gt;31&lt;/span&gt;
&lt;span class="normal"&gt;32&lt;/span&gt;
&lt;span class="normal"&gt;33&lt;/span&gt;
&lt;span class="normal"&gt;34&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/td&gt;&lt;td class="code"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="nf"&gt;SPRITE_ANISE_FRONT_1:&lt;/span&gt;
    &lt;span class="k"&gt;dw `00000111&lt;/span&gt;
    &lt;span class="k"&gt;dw `00001222&lt;/span&gt;
    &lt;span class="k"&gt;dw `00012222&lt;/span&gt;
    &lt;span class="k"&gt;dw `00121222&lt;/span&gt;
    &lt;span class="k"&gt;dw `00121122&lt;/span&gt;
    &lt;span class="k"&gt;dw `00121111&lt;/span&gt;
    &lt;span class="k"&gt;dw `00121122&lt;/span&gt;
    &lt;span class="k"&gt;dw `00121312&lt;/span&gt;
    &lt;span class="k"&gt;dw `00121313&lt;/span&gt;
    &lt;span class="k"&gt;dw `00012132&lt;/span&gt;
    &lt;span class="k"&gt;dw `00001211&lt;/span&gt;
    &lt;span class="k"&gt;dw `00000123&lt;/span&gt;
    &lt;span class="k"&gt;dw `00100123&lt;/span&gt;
    &lt;span class="k"&gt;dw `00011133&lt;/span&gt;
    &lt;span class="k"&gt;dw `00000131&lt;/span&gt;
    &lt;span class="k"&gt;dw `00000010&lt;/span&gt;
&lt;span class="nf"&gt;SPRITE_ANISE_FRONT_2:&lt;/span&gt;
    &lt;span class="k"&gt;dw `11100000&lt;/span&gt;
    &lt;span class="k"&gt;dw `22210000&lt;/span&gt;
    &lt;span class="k"&gt;dw `22221000&lt;/span&gt;
    &lt;span class="k"&gt;dw `22212100&lt;/span&gt;
    &lt;span class="k"&gt;dw `22112100&lt;/span&gt;
    &lt;span class="k"&gt;dw `11112100&lt;/span&gt;
    &lt;span class="k"&gt;dw `22112100&lt;/span&gt;
    &lt;span class="k"&gt;dw `21312100&lt;/span&gt;
    &lt;span class="k"&gt;dw `31312100&lt;/span&gt;
    &lt;span class="k"&gt;dw `23121000&lt;/span&gt;
    &lt;span class="k"&gt;dw `11210000&lt;/span&gt;
    &lt;span class="k"&gt;dw `32100000&lt;/span&gt;
    &lt;span class="k"&gt;dw `32100000&lt;/span&gt;
    &lt;span class="k"&gt;dw `33100000&lt;/span&gt;
    &lt;span class="k"&gt;dw `13100000&lt;/span&gt;
    &lt;span class="k"&gt;dw `01000000&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;p&gt;Yes, I am having trouble deciding on a naming&amp;nbsp;convention.&lt;/p&gt;
&lt;p&gt;This is now a 16×16 sprite, made out of two 8×16 parts.  This post has enough code blocks as it is, and the changes to make this work are relatively minor copy/paste work, so the quick version&amp;nbsp;is:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Set the &lt;span class="caps"&gt;LCDC&lt;/span&gt; flag (bit 2, or &lt;code&gt;LCDCF_OBJ16&lt;/code&gt;) that makes objects be 8×16.  This mode uses pairs of tiles, so an object that uses either tile 0 or 1 will draw both of them, with tile 0 on top of tile&amp;nbsp;1.&lt;/li&gt;
&lt;li&gt;Extend the code that loads object tiles to load &lt;em&gt;four&lt;/em&gt;&amp;nbsp;instead.&lt;/li&gt;
&lt;li&gt;Define a second sprite that&amp;#8217;s 8 pixels to the right of the first&amp;nbsp;one.&lt;/li&gt;
&lt;li&gt;Remove the hard-coded object palette, and instead load the &lt;code&gt;PALETTE_ANISE&lt;/code&gt; that I sneakily included above.  This time the registers are called &lt;code&gt;rOCPS&lt;/code&gt; and &lt;code&gt;rOCPD&lt;/code&gt;.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Finally, extend the code that moves the sprite to also move the second&amp;nbsp;half:&lt;/p&gt;
&lt;table class="highlighttable"&gt;&lt;tr&gt;&lt;td class="linenos"&gt;&lt;div class="linenodiv"&gt;&lt;pre&gt;&lt;span class="normal"&gt; 1&lt;/span&gt;
&lt;span class="normal"&gt; 2&lt;/span&gt;
&lt;span class="normal"&gt; 3&lt;/span&gt;
&lt;span class="normal"&gt; 4&lt;/span&gt;
&lt;span class="normal"&gt; 5&lt;/span&gt;
&lt;span class="normal"&gt; 6&lt;/span&gt;
&lt;span class="normal"&gt; 7&lt;/span&gt;
&lt;span class="normal"&gt; 8&lt;/span&gt;
&lt;span class="normal"&gt; 9&lt;/span&gt;
&lt;span class="normal"&gt;10&lt;/span&gt;
&lt;span class="normal"&gt;11&lt;/span&gt;
&lt;span class="normal"&gt;12&lt;/span&gt;
&lt;span class="normal"&gt;13&lt;/span&gt;
&lt;span class="normal"&gt;14&lt;/span&gt;
&lt;span class="normal"&gt;15&lt;/span&gt;
&lt;span class="normal"&gt;16&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/td&gt;&lt;td class="code"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;    &lt;span class="c1"&gt;; Finally, write the new coordinates back to the OAM&lt;/span&gt;
    &lt;span class="c1"&gt;; buffer, which hl is still pointing into&lt;/span&gt;
    &lt;span class="ow"&gt;ld&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;hl&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="nb"&gt;c&lt;/span&gt;
    &lt;span class="ow"&gt;dec&lt;/span&gt; &lt;span class="nb"&gt;hl&lt;/span&gt;
    &lt;span class="ow"&gt;ld&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;hl&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="nb"&gt;b&lt;/span&gt;
    &lt;span class="c1"&gt;; This bit is new: copy the x-coord into a so I can add 8&lt;/span&gt;
    &lt;span class="c1"&gt;; to it, then store both coords into the second sprite&amp;#39;s&lt;/span&gt;
    &lt;span class="c1"&gt;; OAM data&lt;/span&gt;
    &lt;span class="ow"&gt;ld&lt;/span&gt; &lt;span class="nb"&gt;a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;c&lt;/span&gt;
    &lt;span class="ow"&gt;add&lt;/span&gt; &lt;span class="nb"&gt;a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;8&lt;/span&gt;
    &lt;span class="c1"&gt;; I could&amp;#39;ve written this the other way around, but I did&lt;/span&gt;
    &lt;span class="c1"&gt;; not, I guess because this structure mirrors the above?&lt;/span&gt;
    &lt;span class="ow"&gt;ld&lt;/span&gt; &lt;span class="nb"&gt;hl&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;oam_buffer&lt;/span&gt; &lt;span class="p"&gt;+&lt;/span&gt; &lt;span class="nv"&gt;5&lt;/span&gt;
    &lt;span class="ow"&gt;ld&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;hl&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="nb"&gt;a&lt;/span&gt;
    &lt;span class="ow"&gt;dec&lt;/span&gt; &lt;span class="nb"&gt;hl&lt;/span&gt;
    &lt;span class="ow"&gt;ld&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;hl&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="nb"&gt;b&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;p&gt;Cross my fingers,&amp;nbsp;and…&lt;/p&gt;
&lt;div class="prose-full-illustration"&gt;
&lt;img src="https://eev.ee/media/cheezball/02d-anise-move-grass.gif" alt="A little cat sprite atop the grassy background"&gt;
&lt;/div&gt;

&lt;p&gt;Hey hey hey!  That finally looks like&amp;nbsp;something!&lt;/p&gt;
&lt;h2 id="to-be-continued"&gt;&lt;a class="toclink" href="#to-be-continued"&gt;To be&amp;nbsp;continued&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;It was a surprisingly long journey, but this brings us more or less up to &lt;a href="https://github.com/eevee/anise-cheezball-rising/commit/313a3e1aee596270530cc1bf40db3df2a1e69535"&gt;commit &lt;code&gt;313a3e&lt;/code&gt;&lt;/a&gt;, which happens to be the first commit I made a release of!  It&amp;#8217;s been more than a week, so you can grab it on &lt;a href="https://www.patreon.com/posts/cheezball-rising-19556867"&gt;Patreon&lt;/a&gt; or &lt;a href="https://github.com/eevee/anise-cheezball-rising/releases/tag/v20180619pre"&gt;GitHub&lt;/a&gt;.  I strongly recommend playing it with a release of mGBA prior to 0.7, for…  reasons that will become clear next&amp;nbsp;time.&lt;/p&gt;
&lt;p&gt;Next time: &lt;a href="https://eev.ee/blog/2018/07/13/cheezball-rising-spring-cleaning/"&gt;I&amp;#8217;ll take a breather and clean up a few things&lt;/a&gt;.&lt;/p&gt;</content><category term="blog"></category><category term="tech"></category><category term="gamedev"></category><category term="cheezball rising"></category></entry><entry><title>Cheezball Rising: Drawing a sprite</title><link href="https://eev.ee/blog/2018/06/21/cheezball-rising-drawing-a-sprite/" rel="alternate"></link><published>2018-06-21T12:50:00-07:00</published><updated>2018-06-21T12:50:00-07:00</updated><author><name>Eevee</name></author><id>tag:eev.ee,2018-06-21:/blog/2018/06/21/cheezball-rising-drawing-a-sprite/</id><summary type="html">&lt;p&gt;This is a series about &lt;a href="https://github.com/eevee/anise-cheezball-rising"&gt;&lt;strong&gt;Star Anise Chronicles: Cheezball Rising&lt;/strong&gt;&lt;/a&gt;, an expansive adventure game about my cat for the Game Boy Color.  Follow along as I struggle to make something with this bleeding-edge console!&lt;/p&gt;
&lt;p&gt;GitHub has &lt;a href="https://github.com/eevee/anise-cheezball-rising/releases"&gt;intermittent prebuilt ROMs&lt;/a&gt;, or you can get them a week early &lt;a href="https://www.patreon.com/eevee/posts?tag=cheezball%20rising"&gt;on Patreon&lt;/a&gt; if you pledge $4.  More details in the &lt;a href="https://github.com/eevee/anise-cheezball-rising"&gt;&lt;span class="caps"&gt;README&lt;/span&gt;&lt;/a&gt;!&lt;/p&gt;
&lt;hr/&gt;
&lt;p&gt;In this issue, I figure out how to draw a sprite.  &lt;em&gt;This part was hard.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;Previously: &lt;a href="https://eev.ee/blog/2018/06/19/cheezball-rising-a-new-game-boy-color-game/"&gt;figuring out how to put literally anything on the goddamn screen&lt;/a&gt;.&lt;br/&gt;
Next: &lt;a href="https://eev.ee/blog/2018/07/05/cheezball-rising-main-loop-input-and-a-game/"&gt;the remaining bits necessary to have something game-like&lt;/a&gt;.&lt;/p&gt;
</summary><content type="html">&lt;p&gt;This is a series about &lt;a href="https://github.com/eevee/anise-cheezball-rising"&gt;&lt;strong&gt;Star Anise Chronicles: Cheezball Rising&lt;/strong&gt;&lt;/a&gt;, an expansive adventure game about my cat for the Game Boy Color.  Follow along as I struggle to make something with this bleeding-edge&amp;nbsp;console!&lt;/p&gt;
&lt;p&gt;GitHub has &lt;a href="https://github.com/eevee/anise-cheezball-rising/releases"&gt;intermittent prebuilt ROMs&lt;/a&gt;, or you can get them a week early &lt;a href="https://www.patreon.com/eevee/posts?tag=cheezball%20rising"&gt;on Patreon&lt;/a&gt; if you pledge $4.  More details in the &lt;a href="https://github.com/eevee/anise-cheezball-rising"&gt;&lt;span class="caps"&gt;README&lt;/span&gt;&lt;/a&gt;!&lt;/p&gt;
&lt;hr /&gt;
&lt;p&gt;In this issue, I figure out how to draw a sprite.  &lt;em&gt;This part was&amp;nbsp;hard.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;Previously: &lt;a href="https://eev.ee/blog/2018/06/19/cheezball-rising-a-new-game-boy-color-game/"&gt;figuring out how to put literally anything on the goddamn screen&lt;/a&gt;.&lt;br /&gt;
Next: &lt;a href="https://eev.ee/blog/2018/07/05/cheezball-rising-main-loop-input-and-a-game/"&gt;the remaining bits necessary to have something game-like&lt;/a&gt;.&lt;/p&gt;


&lt;h2 id="recap"&gt;&lt;a class="toclink" href="#recap"&gt;Recap&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Welcome back!  I&amp;#8217;ve started cobbling together a Pygments lexer for &lt;span class="caps"&gt;RGBDS&lt;/span&gt;&amp;#8217;s assembly flavor, so hopefully the code blocks are more readable, and will become moreso over&amp;nbsp;time.&lt;/p&gt;
&lt;p&gt;When I left off last time, I had&amp;#8230;  um&amp;#8230;&amp;nbsp;this.&lt;/p&gt;
&lt;div class="prose-full-illustration"&gt;
&lt;img src="https://eev.ee/media/cheezball/00d-color-gradient.png" alt="Vertical stripes of red, green, blue, and white"&gt;
&lt;/div&gt;

&lt;p&gt;This is all on the &lt;em&gt;background&lt;/em&gt; layer, which I mentioned before is a fixed grid of 8×8&amp;nbsp;tiles.&lt;/p&gt;
&lt;p&gt;For anything that moves around freely, like the player, I need to use the &lt;em&gt;object&lt;/em&gt; layer.  So that&amp;#8217;s an obvious place to go&amp;nbsp;next.&lt;/p&gt;
&lt;p&gt;Now, if you remember, I can define &lt;em&gt;tiles&lt;/em&gt; by just writing to video &lt;span class="caps"&gt;RAM&lt;/span&gt;, and I define &lt;em&gt;palettes&lt;/em&gt; with a goofy system involving writing them one byte at a time to the same magic address.  You might expect defining &lt;em&gt;objects&lt;/em&gt; to do some third completely different thing, and you&amp;#8217;d be&amp;nbsp;right!&lt;/p&gt;
&lt;h2 id="defining-an-object"&gt;&lt;a class="toclink" href="#defining-an-object"&gt;Defining an&amp;nbsp;object&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Objects are defined in their own little chunk of &lt;span class="caps"&gt;RAM&lt;/span&gt; called &lt;span class="caps"&gt;OAM&lt;/span&gt;, for &lt;em&gt;object attribute memory&lt;/em&gt;.  They&amp;#8217;re also made up of tiles, but each tile can be positioned at an arbitrary point on the&amp;nbsp;screen.&lt;/p&gt;
&lt;p&gt;&lt;span class="caps"&gt;OAM&lt;/span&gt; starts at $fe00 and each object takes four bytes — the y-coordinate, the x-coordinate, the tile number, and some flags — for a total of 160 bytes.  The coordinates are offset such that (8, 16) is the top-left of the screen, which both allows all zeroes to mean &amp;#8220;no object&amp;#8221; and allows for objects to be drawn partially&amp;nbsp;offscreen.&lt;/p&gt;
&lt;p&gt;Here&amp;#8217;s the fun part: I can&amp;#8217;t write directly to &lt;span class="caps"&gt;OAM&lt;/span&gt;?  I guess???  Come to think of it, I don&amp;#8217;t think the manual explicitly says I can&amp;#8217;t, but it&amp;#8217;s &lt;em&gt;strongly implied&lt;/em&gt;.  Hmm.  I&amp;#8217;ll look into that.  But I didn&amp;#8217;t at the time, so I&amp;#8217;ll continue under the assumption that the following nonsense is&amp;nbsp;necessary.&lt;/p&gt;
&lt;p&gt;Because I &amp;#8220;can&amp;#8217;t&amp;#8221; write directly, I need to use some &lt;em&gt;shenanigans&lt;/em&gt;.  First, I need something to write!  This is an Anise game, so let&amp;#8217;s go for&amp;nbsp;Anise.&lt;/p&gt;
&lt;p&gt;I&amp;#8217;m on my laptop at this point without access to the source code for the LÖVE Anise game I started, so I have to rustle up a screenshot I&amp;nbsp;took.&lt;/p&gt;
&lt;div class="prose-full-illustration"&gt;
&lt;img src="https://eev.ee/media/cheezball/01a-hi-def-star-anise.png" alt="Cropped screenshot of Star Anise and some critters, all pixel art"&gt;
&lt;/div&gt;

&lt;p&gt;Wait a&amp;nbsp;second.&lt;/p&gt;
&lt;p&gt;Even on the Game Boy Color, tiles are defined with two bits per pixel.  That means an 8×8 tile has a maximum of four colors.  For objects, the first color is transparent, so I really have &lt;em&gt;three&lt;/em&gt; colors — which is exactly why most Game Boy Color protagonists have a main color, an outline/shadow color, and a highlight&amp;nbsp;color.&lt;/p&gt;
&lt;p&gt;Let&amp;#8217;s check out that Anise in more&amp;nbsp;detail.&lt;/p&gt;
&lt;div class="prose-full-illustration"&gt;
&lt;img src="https://eev.ee/media/cheezball/01b-hi-def-star-anise-zoom.png" alt="Star Anise at 8×"&gt;
&lt;/div&gt;

&lt;p&gt;Hm yes okay that&amp;#8217;s more than three colors.  I guess I&amp;#8217;m going to need to draw some new sprites from scratch,&amp;nbsp;somehow.&lt;/p&gt;
&lt;p&gt;In the meantime, I optimistically notice that Star Anise&amp;#8217;s body only uses three colors, &lt;em&gt;and&lt;/em&gt; it&amp;#8217;s 8×7!  I could make a tile out of that!  I painstakingly copy the pixels into a block of those backticks, which you can kinda see is his body if you squint a&amp;nbsp;bit:&lt;/p&gt;
&lt;table class="highlighttable"&gt;&lt;tr&gt;&lt;td class="linenos"&gt;&lt;div class="linenodiv"&gt;&lt;pre&gt;&lt;span class="normal"&gt; 1&lt;/span&gt;
&lt;span class="normal"&gt; 2&lt;/span&gt;
&lt;span class="normal"&gt; 3&lt;/span&gt;
&lt;span class="normal"&gt; 4&lt;/span&gt;
&lt;span class="normal"&gt; 5&lt;/span&gt;
&lt;span class="normal"&gt; 6&lt;/span&gt;
&lt;span class="normal"&gt; 7&lt;/span&gt;
&lt;span class="normal"&gt; 8&lt;/span&gt;
&lt;span class="normal"&gt; 9&lt;/span&gt;
&lt;span class="normal"&gt;10&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/td&gt;&lt;td class="code"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="k"&gt;SECTION &amp;quot;Sprites&amp;quot;, ROM0&lt;/span&gt;
&lt;span class="nf"&gt;ANISE_SPRITE:&lt;/span&gt;
    &lt;span class="k"&gt;dw `00000000&lt;/span&gt;
    &lt;span class="k"&gt;dw `00001333&lt;/span&gt;
    &lt;span class="k"&gt;dw `00001323&lt;/span&gt;
    &lt;span class="k"&gt;dw `10001233&lt;/span&gt;
    &lt;span class="k"&gt;dw `01001333&lt;/span&gt;
    &lt;span class="k"&gt;dw `00113332&lt;/span&gt;
    &lt;span class="k"&gt;dw `00003002&lt;/span&gt;
    &lt;span class="k"&gt;dw `00003002&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;p&gt;The &lt;code&gt;dw&lt;/code&gt; notation isn&amp;#8217;t an opcode; it tells the assembler to put two literal bytes of data in the final &lt;span class="caps"&gt;ROM&lt;/span&gt;.  A &lt;em&gt;word&lt;/em&gt; of &lt;em&gt;data&lt;/em&gt;.  (Each row of a tile is two bytes,&amp;nbsp;remember.)&lt;/p&gt;
&lt;p&gt;If you think about this too hard, you start to realize that both the data and code are &lt;em&gt;just bytes&lt;/em&gt;, everything is arbitrary, and true meaning is found only in the &lt;em&gt;way&lt;/em&gt; we perceive things rather than in the things&amp;nbsp;themselves.&lt;/p&gt;
&lt;p&gt;Note I didn&amp;#8217;t specify an exact address for this section, so the linker will figure out somewhere to put it and make sure all the labels are right at the&amp;nbsp;end.&lt;/p&gt;
&lt;p&gt;Now I load this into tilespace, back in my main&amp;nbsp;code:&lt;/p&gt;
&lt;table class="highlighttable"&gt;&lt;tr&gt;&lt;td class="linenos"&gt;&lt;div class="linenodiv"&gt;&lt;pre&gt;&lt;span class="normal"&gt;1&lt;/span&gt;
&lt;span class="normal"&gt;2&lt;/span&gt;
&lt;span class="normal"&gt;3&lt;/span&gt;
&lt;span class="normal"&gt;4&lt;/span&gt;
&lt;span class="normal"&gt;5&lt;/span&gt;
&lt;span class="normal"&gt;6&lt;/span&gt;
&lt;span class="normal"&gt;7&lt;/span&gt;
&lt;span class="normal"&gt;8&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/td&gt;&lt;td class="code"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;    &lt;span class="c1"&gt;; Define an object&lt;/span&gt;
    &lt;span class="ow"&gt;ld&lt;/span&gt; &lt;span class="nb"&gt;hl&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mh"&gt;$8800&lt;/span&gt;
    &lt;span class="ow"&gt;ld&lt;/span&gt; &lt;span class="nb"&gt;bc&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;ANISE_SPRITE&lt;/span&gt;
    &lt;span class="k"&gt;REPT 16&lt;/span&gt;
    &lt;span class="ow"&gt;ld&lt;/span&gt; &lt;span class="nb"&gt;a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;bc&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="ow"&gt;ld&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;hl&lt;/span&gt;&lt;span class="p"&gt;+],&lt;/span&gt; &lt;span class="nb"&gt;a&lt;/span&gt;
    &lt;span class="ow"&gt;inc&lt;/span&gt; &lt;span class="nb"&gt;bc&lt;/span&gt;
    &lt;span class="k"&gt;ENDR&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;p&gt;This copies 16 bytes, starting from the &lt;code&gt;ANISE_SPRITE&lt;/code&gt; label, to&amp;nbsp;$8800.&lt;/p&gt;
&lt;hr /&gt;
&lt;p&gt;Why $8800, not $8000?  &lt;em&gt;I&amp;#8217;m so glad you&amp;nbsp;asked!&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;There are actually &lt;em&gt;three&lt;/em&gt; blocks of tile space, each with enough room for 128 tiles: one at $8000, one at $8800, and one at $9000.  Object tiles always use the $8000 block followed by the $8800 block, whereas background tiles can use &lt;em&gt;either&lt;/em&gt; $8000 + $8800 or $9000 + $8800.  By default, background tiles use $8000 +&amp;nbsp;$8800.&lt;/p&gt;
&lt;p&gt;All of which is to say that I got very confused reading the manual (which spends like five pages explaining the above paragraph) and put the object tiles in the wrong place.  Whoops.  It&amp;#8217;s fine; this just ends up being tile&amp;nbsp;128.&lt;/p&gt;
&lt;p&gt;In my partial defense, looking at it now, I see the manual is &lt;em&gt;wrong&lt;/em&gt;!  Bit 4 of the &lt;span class="caps"&gt;LCD&lt;/span&gt; controller register ($ff40) controls whether the background uses tiles from $8000 + $8800 (1) or $9000 + $8800 (0).  The manual says that this register defaults to $83, which has bit 4 off, suggesting that background tiles use $9000 + $8800 (i.e. start at $8800), but disassembly of the boot &lt;span class="caps"&gt;ROM&lt;/span&gt; shows that it actually defaults to $91, which has bit 4 &lt;em&gt;on&lt;/em&gt;.  Thanks a lot,&amp;nbsp;Nintendo!&lt;/p&gt;
&lt;p&gt;That was quite a diversion.  Here&amp;#8217;s a chart of where the dang tiles live.  Note that the block at $8800 is always shared between objects and background tiles.  Oh, and on the Game Boy Color, all three blocks are twice as big thanks to the magic of banking.  I&amp;#8217;ll get to banking&amp;#8230;  much&amp;nbsp;later.&lt;/p&gt;
&lt;table class="highlighttable"&gt;&lt;tr&gt;&lt;td class="linenos"&gt;&lt;div class="linenodiv"&gt;&lt;pre&gt;&lt;span class="normal"&gt;1&lt;/span&gt;
&lt;span class="normal"&gt;2&lt;/span&gt;
&lt;span class="normal"&gt;3&lt;/span&gt;
&lt;span class="normal"&gt;4&lt;/span&gt;
&lt;span class="normal"&gt;5&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/td&gt;&lt;td class="code"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;                            bit 4 ON (default)  bit 4 OFF
                            ------------------  ---------
$8000   obj tiles 0-127     bg tiles 0-127
$8800   obj tiles 128-255   bg tiles 128-255    bg tiles 128-255
$9000                                           bg tiles 0-127
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;hr /&gt;
&lt;p&gt;Hokay.  What else?  I&amp;#8217;m going to need a palette for this, and I don&amp;#8217;t want to use that gaudy background palette.  Actually, I &lt;em&gt;can&amp;#8217;t&lt;/em&gt; — the background and object layers have two completely separate sets of&amp;nbsp;palettes.&lt;/p&gt;
&lt;p&gt;Writing an object palette is exactly the same as writing a background palette, except with different&amp;nbsp;registers.&lt;/p&gt;
&lt;table class="highlighttable"&gt;&lt;tr&gt;&lt;td class="linenos"&gt;&lt;div class="linenodiv"&gt;&lt;pre&gt;&lt;span class="normal"&gt; 1&lt;/span&gt;
&lt;span class="normal"&gt; 2&lt;/span&gt;
&lt;span class="normal"&gt; 3&lt;/span&gt;
&lt;span class="normal"&gt; 4&lt;/span&gt;
&lt;span class="normal"&gt; 5&lt;/span&gt;
&lt;span class="normal"&gt; 6&lt;/span&gt;
&lt;span class="normal"&gt; 7&lt;/span&gt;
&lt;span class="normal"&gt; 8&lt;/span&gt;
&lt;span class="normal"&gt; 9&lt;/span&gt;
&lt;span class="normal"&gt;10&lt;/span&gt;
&lt;span class="normal"&gt;11&lt;/span&gt;
&lt;span class="normal"&gt;12&lt;/span&gt;
&lt;span class="normal"&gt;13&lt;/span&gt;
&lt;span class="normal"&gt;14&lt;/span&gt;
&lt;span class="normal"&gt;15&lt;/span&gt;
&lt;span class="normal"&gt;16&lt;/span&gt;
&lt;span class="normal"&gt;17&lt;/span&gt;
&lt;span class="normal"&gt;18&lt;/span&gt;
&lt;span class="normal"&gt;19&lt;/span&gt;
&lt;span class="normal"&gt;20&lt;/span&gt;
&lt;span class="normal"&gt;21&lt;/span&gt;
&lt;span class="normal"&gt;22&lt;/span&gt;
&lt;span class="normal"&gt;23&lt;/span&gt;
&lt;span class="normal"&gt;24&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/td&gt;&lt;td class="code"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;    &lt;span class="c1"&gt;; This should look pretty familiar&lt;/span&gt;
    &lt;span class="ow"&gt;ld&lt;/span&gt; &lt;span class="nb"&gt;a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mb"&gt;%10000000&lt;/span&gt;
    &lt;span class="ow"&gt;ld&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mh"&gt;$ff6a&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="nb"&gt;a&lt;/span&gt;

    &lt;span class="ow"&gt;ld&lt;/span&gt; &lt;span class="nb"&gt;bc&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mb"&gt;%0000000000000000&lt;/span&gt;  &lt;span class="c1"&gt;; transparent&lt;/span&gt;
    &lt;span class="ow"&gt;ld&lt;/span&gt; &lt;span class="nb"&gt;a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;c&lt;/span&gt;
    &lt;span class="ow"&gt;ld&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mh"&gt;$ff6b&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="nb"&gt;a&lt;/span&gt;
    &lt;span class="ow"&gt;ld&lt;/span&gt; &lt;span class="nb"&gt;a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;b&lt;/span&gt;
    &lt;span class="ow"&gt;ld&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mh"&gt;$ff6b&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="nb"&gt;a&lt;/span&gt;
    &lt;span class="ow"&gt;ld&lt;/span&gt; &lt;span class="nb"&gt;bc&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mb"&gt;%0010110100100101&lt;/span&gt;  &lt;span class="c1"&gt;; dark&lt;/span&gt;
    &lt;span class="ow"&gt;ld&lt;/span&gt; &lt;span class="nb"&gt;a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;c&lt;/span&gt;
    &lt;span class="ow"&gt;ld&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mh"&gt;$ff6b&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="nb"&gt;a&lt;/span&gt;
    &lt;span class="ow"&gt;ld&lt;/span&gt; &lt;span class="nb"&gt;a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;b&lt;/span&gt;
    &lt;span class="ow"&gt;ld&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mh"&gt;$ff6b&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="nb"&gt;a&lt;/span&gt;
    &lt;span class="ow"&gt;ld&lt;/span&gt; &lt;span class="nb"&gt;bc&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mb"&gt;%0100000111001101&lt;/span&gt;  &lt;span class="c1"&gt;; med&lt;/span&gt;
    &lt;span class="ow"&gt;ld&lt;/span&gt; &lt;span class="nb"&gt;a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;c&lt;/span&gt;
    &lt;span class="ow"&gt;ld&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mh"&gt;$ff6b&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="nb"&gt;a&lt;/span&gt;
    &lt;span class="ow"&gt;ld&lt;/span&gt; &lt;span class="nb"&gt;a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;b&lt;/span&gt;
    &lt;span class="ow"&gt;ld&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mh"&gt;$ff6b&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="nb"&gt;a&lt;/span&gt;
    &lt;span class="ow"&gt;ld&lt;/span&gt; &lt;span class="nb"&gt;bc&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mb"&gt;%0100001000010001&lt;/span&gt;  &lt;span class="c1"&gt;; white&lt;/span&gt;
    &lt;span class="ow"&gt;ld&lt;/span&gt; &lt;span class="nb"&gt;a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;c&lt;/span&gt;
    &lt;span class="ow"&gt;ld&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mh"&gt;$ff6b&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="nb"&gt;a&lt;/span&gt;
    &lt;span class="ow"&gt;ld&lt;/span&gt; &lt;span class="nb"&gt;a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;b&lt;/span&gt;
    &lt;span class="ow"&gt;ld&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mh"&gt;$ff6b&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="nb"&gt;a&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;p&gt;Riveting!&lt;/p&gt;
&lt;p&gt;I wrote out those colors by hand.  The original dark color, for example, was &lt;code&gt;#264a59&lt;/code&gt;.  That uses eight bits per channel, but the Game Boy Color only supports five (a factor of 8 difference), so first I rounded each channel to the nearest 8 and got &lt;code&gt;#284858&lt;/code&gt;.  Swap the channels to get &lt;code&gt;58 48 28&lt;/code&gt; and convert to binary (sans the trailing zeroes) to get &lt;code&gt;01011 01001 00101&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Note to self: probably write a macro or whatever so I can define colors like a goddamn human being.  Also why am I not putting the colors in a &lt;span class="caps"&gt;ROM&lt;/span&gt; section&amp;nbsp;too?&lt;/p&gt;
&lt;p&gt;Almost there.  I still need to write out those four bytes that specify the tile and where it goes.  I can&amp;#8217;t actually write them to &lt;span class="caps"&gt;OAM&lt;/span&gt; yet, so I need some scratch space in regular &lt;span class="caps"&gt;RAM&lt;/span&gt; — &lt;em&gt;working &lt;span class="caps"&gt;RAM&lt;/span&gt;&lt;/em&gt;.&lt;/p&gt;
&lt;table class="highlighttable"&gt;&lt;tr&gt;&lt;td class="linenos"&gt;&lt;div class="linenodiv"&gt;&lt;pre&gt;&lt;span class="normal"&gt;1&lt;/span&gt;
&lt;span class="normal"&gt;2&lt;/span&gt;
&lt;span class="normal"&gt;3&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/td&gt;&lt;td class="code"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="k"&gt;SECTION &amp;quot;OAM Buffer&amp;quot;, WRAM0[$C100]&lt;/span&gt;
&lt;span class="nf"&gt;oam_buffer:&lt;/span&gt;
    &lt;span class="k"&gt;ds 4 * 40&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;p&gt;The &lt;code&gt;ds&lt;/code&gt; notation is another &amp;#8220;data&amp;#8221; variant, except it can take a size and reserves space for a whole &lt;em&gt;string&lt;/em&gt; of data.  Note that I didn&amp;#8217;t put any actual data here — this section is in &lt;span class="caps"&gt;RAM&lt;/span&gt;, which only exists while the game is running, so there&amp;#8217;d be nowhere to &lt;em&gt;put&lt;/em&gt;&amp;nbsp;data.&lt;/p&gt;
&lt;p&gt;Also note that I gave an explicit address this time.  The buffer has to start at an address ending in 00, for reasons that will become clear momentarily.  The space from $c000 to $dfff is available as working &lt;span class="caps"&gt;RAM&lt;/span&gt;, and I chose $c100 for…  reasons that will also become clear&amp;nbsp;momentarily.&lt;/p&gt;
&lt;p&gt;Now to write four bytes to it at&amp;nbsp;runtime:&lt;/p&gt;
&lt;table class="highlighttable"&gt;&lt;tr&gt;&lt;td class="linenos"&gt;&lt;div class="linenodiv"&gt;&lt;pre&gt;&lt;span class="normal"&gt; 1&lt;/span&gt;
&lt;span class="normal"&gt; 2&lt;/span&gt;
&lt;span class="normal"&gt; 3&lt;/span&gt;
&lt;span class="normal"&gt; 4&lt;/span&gt;
&lt;span class="normal"&gt; 5&lt;/span&gt;
&lt;span class="normal"&gt; 6&lt;/span&gt;
&lt;span class="normal"&gt; 7&lt;/span&gt;
&lt;span class="normal"&gt; 8&lt;/span&gt;
&lt;span class="normal"&gt; 9&lt;/span&gt;
&lt;span class="normal"&gt;10&lt;/span&gt;
&lt;span class="normal"&gt;11&lt;/span&gt;
&lt;span class="normal"&gt;12&lt;/span&gt;
&lt;span class="normal"&gt;13&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/td&gt;&lt;td class="code"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;    &lt;span class="c1"&gt;; Put an object on the screen&lt;/span&gt;
    &lt;span class="ow"&gt;ld&lt;/span&gt; &lt;span class="nb"&gt;hl&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;oam_buffer&lt;/span&gt;
    &lt;span class="c1"&gt;; y-coord&lt;/span&gt;
    &lt;span class="ow"&gt;ld&lt;/span&gt; &lt;span class="nb"&gt;a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;64&lt;/span&gt;
    &lt;span class="ow"&gt;ld&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;hl&lt;/span&gt;&lt;span class="p"&gt;+],&lt;/span&gt; &lt;span class="nb"&gt;a&lt;/span&gt;
    &lt;span class="c1"&gt;; x-coord&lt;/span&gt;
    &lt;span class="ow"&gt;ld&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;hl&lt;/span&gt;&lt;span class="p"&gt;+],&lt;/span&gt; &lt;span class="nb"&gt;a&lt;/span&gt;
    &lt;span class="c1"&gt;; tile index&lt;/span&gt;
    &lt;span class="ow"&gt;ld&lt;/span&gt; &lt;span class="nb"&gt;a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;128&lt;/span&gt;
    &lt;span class="ow"&gt;ld&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;hl&lt;/span&gt;&lt;span class="p"&gt;+],&lt;/span&gt; &lt;span class="nb"&gt;a&lt;/span&gt;
    &lt;span class="c1"&gt;; attributes, including palette, which are all zero&lt;/span&gt;
    &lt;span class="ow"&gt;ld&lt;/span&gt; &lt;span class="nb"&gt;a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mb"&gt;%00000000&lt;/span&gt;
    &lt;span class="ow"&gt;ld&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;hl&lt;/span&gt;&lt;span class="p"&gt;+],&lt;/span&gt; &lt;span class="nb"&gt;a&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;p&gt;(I tried writing directly to &lt;span class="caps"&gt;OAM&lt;/span&gt; on my first attempt.  Nothing happened!  Very&amp;nbsp;exciting.)&lt;/p&gt;
&lt;p&gt;But how to get this into &lt;span class="caps"&gt;OAM&lt;/span&gt; so it&amp;#8217;ll actually show on-screen?  For that, I need to do a &lt;em&gt;&lt;span class="caps"&gt;DMA&lt;/span&gt; transfer&lt;/em&gt;.&lt;/p&gt;
&lt;h2 id="dma"&gt;&lt;a class="toclink" href="#dma"&gt;DMA&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;&lt;span class="caps"&gt;DMA&lt;/span&gt;, or &lt;em&gt;direct memory access&lt;/em&gt;, is one of those things the Game Boy programming manual seems to think everyone is already familiar with.  It refers generally to features that allow some other hardware to access memory, without going through the &lt;span class="caps"&gt;CPU&lt;/span&gt;.  In the case of the Game Boy, it&amp;#8217;s used to copy data from working &lt;span class="caps"&gt;RAM&lt;/span&gt; to &lt;span class="caps"&gt;OAM&lt;/span&gt;.  &lt;em&gt;Only&lt;/em&gt; to &lt;span class="caps"&gt;OAM&lt;/span&gt;.  It&amp;#8217;s very&amp;nbsp;specific.&lt;/p&gt;
&lt;p&gt;Performing a &lt;span class="caps"&gt;DMA&lt;/span&gt; transfer is super easy!  I write the high byte of the &lt;em&gt;source&lt;/em&gt; address to the &lt;span class="caps"&gt;DMA&lt;/span&gt; register ($ff46), and then &lt;em&gt;some magic happens&lt;/em&gt;, and 160 bytes from the source address appear in &lt;span class="caps"&gt;OAM&lt;/span&gt;.  In other&amp;nbsp;words:&lt;/p&gt;
&lt;table class="highlighttable"&gt;&lt;tr&gt;&lt;td class="linenos"&gt;&lt;div class="linenodiv"&gt;&lt;pre&gt;&lt;span class="normal"&gt;1&lt;/span&gt;
&lt;span class="normal"&gt;2&lt;/span&gt;
&lt;span class="normal"&gt;3&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/td&gt;&lt;td class="code"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;    &lt;span class="ow"&gt;ld&lt;/span&gt; &lt;span class="nb"&gt;a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mh"&gt;$c1&lt;/span&gt;       &lt;span class="c1"&gt;; copy from $c100&lt;/span&gt;
    &lt;span class="ow"&gt;ld&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mh"&gt;$ff46&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="nb"&gt;a&lt;/span&gt;   &lt;span class="c1"&gt;; perform DMA transfer&lt;/span&gt;
    &lt;span class="c1"&gt;; now $c000 through $c09f have been copied into OAM!&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;p&gt;It&amp;#8217;s almost too good to be true!  And it is.  There are some&amp;nbsp;wrinkles.&lt;/p&gt;
&lt;p&gt;First, the transfer takes some time, during which I almost certainly don&amp;#8217;t want to be doing anything&amp;nbsp;else.&lt;/p&gt;
&lt;p&gt;Second, during the transfer, the &lt;span class="caps"&gt;CPU&lt;/span&gt; can only read from &amp;#8220;high &lt;span class="caps"&gt;RAM&lt;/span&gt;&amp;#8221; — $ff80 and higher.  Wait, uh&amp;nbsp;oh.&lt;/p&gt;
&lt;p&gt;The usual workaround here is to copy a very short function into high &lt;span class="caps"&gt;RAM&lt;/span&gt; to perform the actual transfer and wait for it to finish, then call &lt;em&gt;that&lt;/em&gt; instead of starting a transfer directly.  Well, that sounds like a pain, so I break my rule of accounting for every byte and &lt;a href="https://exez.in/gameboy-dma"&gt;find someone else who&amp;#8217;s done it&lt;/a&gt;.  Conveniently enough, that post is by the author of the &lt;a href="https://github.com/exezin/gb-template"&gt;small template project&lt;/a&gt; I&amp;#8217;ve been glancing&amp;nbsp;at.&lt;/p&gt;
&lt;p&gt;I end up with something like the&amp;nbsp;following.&lt;/p&gt;
&lt;table class="highlighttable"&gt;&lt;tr&gt;&lt;td class="linenos"&gt;&lt;div class="linenodiv"&gt;&lt;pre&gt;&lt;span class="normal"&gt; 1&lt;/span&gt;
&lt;span class="normal"&gt; 2&lt;/span&gt;
&lt;span class="normal"&gt; 3&lt;/span&gt;
&lt;span class="normal"&gt; 4&lt;/span&gt;
&lt;span class="normal"&gt; 5&lt;/span&gt;
&lt;span class="normal"&gt; 6&lt;/span&gt;
&lt;span class="normal"&gt; 7&lt;/span&gt;
&lt;span class="normal"&gt; 8&lt;/span&gt;
&lt;span class="normal"&gt; 9&lt;/span&gt;
&lt;span class="normal"&gt;10&lt;/span&gt;
&lt;span class="normal"&gt;11&lt;/span&gt;
&lt;span class="normal"&gt;12&lt;/span&gt;
&lt;span class="normal"&gt;13&lt;/span&gt;
&lt;span class="normal"&gt;14&lt;/span&gt;
&lt;span class="normal"&gt;15&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/td&gt;&lt;td class="code"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;    &lt;span class="c1"&gt;; Copy the little DMA routine into high RAM&lt;/span&gt;
    &lt;span class="ow"&gt;ld&lt;/span&gt; &lt;span class="nb"&gt;bc&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;DMA_BYTECODE&lt;/span&gt;
    &lt;span class="ow"&gt;ld&lt;/span&gt; &lt;span class="nb"&gt;hl&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mh"&gt;$ff80&lt;/span&gt;
    &lt;span class="c1"&gt;; DMA routine is 13 bytes long&lt;/span&gt;
    &lt;span class="k"&gt;REPT 13&lt;/span&gt;
    &lt;span class="ow"&gt;ld&lt;/span&gt; &lt;span class="nb"&gt;a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;bc&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="ow"&gt;inc&lt;/span&gt; &lt;span class="nb"&gt;bc&lt;/span&gt;
    &lt;span class="ow"&gt;ld&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;hl&lt;/span&gt;&lt;span class="p"&gt;+],&lt;/span&gt; &lt;span class="nb"&gt;a&lt;/span&gt;
    &lt;span class="k"&gt;ENDR&lt;/span&gt;

&lt;span class="c1"&gt;; ...&lt;/span&gt;

&lt;span class="k"&gt;SECTION &amp;quot;DMA Bytecode&amp;quot;, ROM0&lt;/span&gt;
&lt;span class="nf"&gt;DMA_BYTECODE:&lt;/span&gt;
    &lt;span class="k"&gt;db $F5, $3E, $C1, $EA, $46, $FF, $3E, $28, $3D, $20, $FD, $F1, $D9&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;p&gt;That&amp;#8217;s compiled assembly, written inline as bytes.  Oh boy.  The original code looks&amp;nbsp;like:&lt;/p&gt;
&lt;table class="highlighttable"&gt;&lt;tr&gt;&lt;td class="linenos"&gt;&lt;div class="linenodiv"&gt;&lt;pre&gt;&lt;span class="normal"&gt; 1&lt;/span&gt;
&lt;span class="normal"&gt; 2&lt;/span&gt;
&lt;span class="normal"&gt; 3&lt;/span&gt;
&lt;span class="normal"&gt; 4&lt;/span&gt;
&lt;span class="normal"&gt; 5&lt;/span&gt;
&lt;span class="normal"&gt; 6&lt;/span&gt;
&lt;span class="normal"&gt; 7&lt;/span&gt;
&lt;span class="normal"&gt; 8&lt;/span&gt;
&lt;span class="normal"&gt; 9&lt;/span&gt;
&lt;span class="normal"&gt;10&lt;/span&gt;
&lt;span class="normal"&gt;11&lt;/span&gt;
&lt;span class="normal"&gt;12&lt;/span&gt;
&lt;span class="normal"&gt;13&lt;/span&gt;
&lt;span class="normal"&gt;14&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/td&gt;&lt;td class="code"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;    &lt;span class="c1"&gt;; start the transfer, as shown above&lt;/span&gt;
    &lt;span class="ow"&gt;ld&lt;/span&gt; &lt;span class="nb"&gt;a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mh"&gt;$c1&lt;/span&gt;
    &lt;span class="ow"&gt;ld&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mh"&gt;$ff46&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="nb"&gt;a&lt;/span&gt;

    &lt;span class="c1"&gt;; wait 160 cycles/microseconds, the time it takes for the&lt;/span&gt;
    &lt;span class="c1"&gt;; transfer to finish; this works because &amp;#39;dec&amp;#39; is 1 cycle&lt;/span&gt;
    &lt;span class="c1"&gt;; and &amp;#39;jr&amp;#39; is 3, for 4 cycles done 40 times&lt;/span&gt;
    &lt;span class="ow"&gt;ld&lt;/span&gt;      &lt;span class="nb"&gt;a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;40&lt;/span&gt;
&lt;span class="nf"&gt;loop:&lt;/span&gt;
    &lt;span class="ow"&gt;dec&lt;/span&gt;     &lt;span class="nb"&gt;a&lt;/span&gt;
    &lt;span class="ow"&gt;jr&lt;/span&gt;      &lt;span class="nv"&gt;nz&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;loop&lt;/span&gt;

    &lt;span class="c1"&gt;; return&lt;/span&gt;
    &lt;span class="ow"&gt;ret&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;p&gt;Now you can see why I used $c100 for my &lt;span class="caps"&gt;OAM&lt;/span&gt; buffer: because it&amp;#8217;s the address this person&amp;nbsp;used.&lt;/p&gt;
&lt;p&gt;(Hm, the &lt;a href="http://www.pastraiser.com/cpu/gameboy/gameboy_opcodes.html"&gt;opcode reference&lt;/a&gt; I usually use seems to have all the timings multiplied by a factor of 4 without comment?  Odd.  The rgbds &lt;a href="https://rednex.github.io/rgbds/gbz80.7.html"&gt;reference&lt;/a&gt; is&amp;nbsp;correct.)&lt;/p&gt;
&lt;p&gt;(Also, here&amp;#8217;s a fun fact: the stack starts at $fffe and grows backwards.  If it grows too big, the very first thing it&amp;#8217;ll overwrite is this &lt;span class="caps"&gt;DMA&lt;/span&gt; routine!  I bet that&amp;#8217;ll have some fun&amp;nbsp;effects.)&lt;/p&gt;
&lt;p&gt;At this point I have a thought.  (Okay, I had the thought a bit later, but it works better narratively if I have it now.)  I&amp;#8217;ve already demonstrated that the line between code and data is a bit fuzzy here.  So &lt;em&gt;why does this code need to be pre-assembled&lt;/em&gt;?&lt;/p&gt;
&lt;p&gt;And a similar thought: why is the length hardcoded?  Surely, we can do a little better.  What if we shuffle things around a&amp;nbsp;bit&amp;#8230;&lt;/p&gt;
&lt;table class="highlighttable"&gt;&lt;tr&gt;&lt;td class="linenos"&gt;&lt;div class="linenodiv"&gt;&lt;pre&gt;&lt;span class="normal"&gt; 1&lt;/span&gt;
&lt;span class="normal"&gt; 2&lt;/span&gt;
&lt;span class="normal"&gt; 3&lt;/span&gt;
&lt;span class="normal"&gt; 4&lt;/span&gt;
&lt;span class="normal"&gt; 5&lt;/span&gt;
&lt;span class="normal"&gt; 6&lt;/span&gt;
&lt;span class="normal"&gt; 7&lt;/span&gt;
&lt;span class="normal"&gt; 8&lt;/span&gt;
&lt;span class="normal"&gt; 9&lt;/span&gt;
&lt;span class="normal"&gt;10&lt;/span&gt;
&lt;span class="normal"&gt;11&lt;/span&gt;
&lt;span class="normal"&gt;12&lt;/span&gt;
&lt;span class="normal"&gt;13&lt;/span&gt;
&lt;span class="normal"&gt;14&lt;/span&gt;
&lt;span class="normal"&gt;15&lt;/span&gt;
&lt;span class="normal"&gt;16&lt;/span&gt;
&lt;span class="normal"&gt;17&lt;/span&gt;
&lt;span class="normal"&gt;18&lt;/span&gt;
&lt;span class="normal"&gt;19&lt;/span&gt;
&lt;span class="normal"&gt;20&lt;/span&gt;
&lt;span class="normal"&gt;21&lt;/span&gt;
&lt;span class="normal"&gt;22&lt;/span&gt;
&lt;span class="normal"&gt;23&lt;/span&gt;
&lt;span class="normal"&gt;24&lt;/span&gt;
&lt;span class="normal"&gt;25&lt;/span&gt;
&lt;span class="normal"&gt;26&lt;/span&gt;
&lt;span class="normal"&gt;27&lt;/span&gt;
&lt;span class="normal"&gt;28&lt;/span&gt;
&lt;span class="normal"&gt;29&lt;/span&gt;
&lt;span class="normal"&gt;30&lt;/span&gt;
&lt;span class="normal"&gt;31&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/td&gt;&lt;td class="code"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="k"&gt;SECTION &amp;quot;init&amp;quot;, ROM0[$0100]&lt;/span&gt;
    &lt;span class="ow"&gt;nop&lt;/span&gt;
    &lt;span class="c1"&gt;; Jump to a named label instead of an address&lt;/span&gt;
    &lt;span class="ow"&gt;jp&lt;/span&gt; &lt;span class="nv"&gt;main&lt;/span&gt;

&lt;span class="k"&gt;SECTION &amp;quot;main&amp;quot;, ROM0[$0150]&lt;/span&gt;
&lt;span class="c1"&gt;; DMA copy routine, copied into high RAM at startup.&lt;/span&gt;
&lt;span class="c1"&gt;; Never actually called where it is.&lt;/span&gt;
&lt;span class="nf"&gt;dma_copy:&lt;/span&gt;
    &lt;span class="ow"&gt;ld&lt;/span&gt; &lt;span class="nb"&gt;a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mh"&gt;$c1&lt;/span&gt;
    &lt;span class="ow"&gt;ld&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mh"&gt;$ff46&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="nb"&gt;a&lt;/span&gt;
    &lt;span class="ow"&gt;ld&lt;/span&gt; &lt;span class="nb"&gt;a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;40&lt;/span&gt;
&lt;span class="nf"&gt;.loop:&lt;/span&gt;
    &lt;span class="ow"&gt;dec&lt;/span&gt; &lt;span class="nb"&gt;a&lt;/span&gt;
    &lt;span class="ow"&gt;jr&lt;/span&gt; &lt;span class="nv"&gt;nz&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="err"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;loop&lt;/span&gt;
    &lt;span class="ow"&gt;ret&lt;/span&gt;
&lt;span class="nf"&gt;dma_copy_end:&lt;/span&gt;
    &lt;span class="ow"&gt;nop&lt;/span&gt;

&lt;span class="nf"&gt;main:&lt;/span&gt;
    &lt;span class="c1"&gt;; ... all previous code is here now ...&lt;/span&gt;

    &lt;span class="c1"&gt;; Copy the little DMA routine into high RAM&lt;/span&gt;
    &lt;span class="ow"&gt;ld&lt;/span&gt; &lt;span class="nb"&gt;bc&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;dma_copy&lt;/span&gt;
    &lt;span class="ow"&gt;ld&lt;/span&gt; &lt;span class="nb"&gt;hl&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mh"&gt;$ff80&lt;/span&gt;
    &lt;span class="c1"&gt;; DMA routine is 13 bytes long&lt;/span&gt;
    &lt;span class="k"&gt;REPT dma_copy_end - dma_copy&lt;/span&gt;
    &lt;span class="ow"&gt;ld&lt;/span&gt; &lt;span class="nb"&gt;a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;bc&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="ow"&gt;inc&lt;/span&gt; &lt;span class="nb"&gt;bc&lt;/span&gt;
    &lt;span class="ow"&gt;ld&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;hl&lt;/span&gt;&lt;span class="p"&gt;+],&lt;/span&gt; &lt;span class="nb"&gt;a&lt;/span&gt;
    &lt;span class="k"&gt;ENDR&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;p&gt;This is very similar to what I just had, except that the code is left as &lt;em&gt;code&lt;/em&gt;, and its length is computed by having another label at the end — so I&amp;#8217;m free to edit it later if I want to.  It all ends up as bytes in the &lt;span class="caps"&gt;ROM&lt;/span&gt;, so the code ends up exactly the same as writing out the bytes with &lt;code&gt;db&lt;/code&gt;.  Come to think of it, I don&amp;#8217;t even need to hardcode the &lt;code&gt;$c1&lt;/code&gt; there; I could replace it with &lt;code&gt;oam_buffer &amp;gt;&amp;gt; 8&lt;/code&gt; and avoid repeating&amp;nbsp;myself.&lt;/p&gt;
&lt;p&gt;(I put the code at $0150 because rgbasm can&amp;#8217;t subtract labels that appear later in the same source file — I presume because this is done by the single-pass assembler, not the linker — and it just seemed weird to have a floating section lexically &lt;em&gt;before&lt;/em&gt; the entry&amp;nbsp;point.)&lt;/p&gt;
&lt;p&gt;I&amp;#8217;m actually surprised that the author of the above post didn&amp;#8217;t think to do this?  Maybe it&amp;#8217;s dirty even by assembly&amp;nbsp;standards.&lt;/p&gt;
&lt;h2 id="timing-vblank-and-some-cool-trickery"&gt;&lt;a class="toclink" href="#timing-vblank-and-some-cool-trickery"&gt;Timing, vblank, and some cool&amp;nbsp;trickery&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Okay, so, as I was writing that last section, I got really curious about whether and when I&amp;#8217;m &lt;em&gt;actually&lt;/em&gt; allowed to write to &lt;span class="caps"&gt;OAM&lt;/span&gt;.  Or tile &lt;span class="caps"&gt;RAM&lt;/span&gt;, for that&amp;nbsp;matter.&lt;/p&gt;
&lt;p&gt;I found/consulted the Game Boy dev wiki, and &lt;a href="http://gbdev.gg8.se/wiki/articles/Video_Display#FF41_-_STAT_-_LCDC_Status_.28R.2FW.29"&gt;the rules&lt;/a&gt; match what&amp;#8217;s in the manual, albeit with a chart that makes things a little more&amp;nbsp;clear.&lt;/p&gt;
&lt;p&gt;My understanding is as follows.  The &lt;span class="caps"&gt;LCD&lt;/span&gt; draws the screen one row of pixels at a time, and each row has the following&amp;nbsp;steps:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;Look through &lt;span class="caps"&gt;OAM&lt;/span&gt; to see if any sprites are on this row.  &lt;span class="caps"&gt;OAM&lt;/span&gt; is inaccessible to the &lt;span class="caps"&gt;CPU&lt;/span&gt;.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Draw the row.  &lt;span class="caps"&gt;OAM&lt;/span&gt;, &lt;span class="caps"&gt;VRAM&lt;/span&gt;, and palettes are all&amp;nbsp;inaccessible.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Finish the row and continue on to the beginning of the next row.  This takes a nonzero amount of time, called the &lt;em&gt;horizontal blanking period&lt;/em&gt;, during which the &lt;span class="caps"&gt;CPU&lt;/span&gt; can access everything&amp;nbsp;freely.&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Once the &lt;span class="caps"&gt;LCD&lt;/span&gt; reaches the bottom, it continues to &amp;#8220;draw&amp;#8221; a number of faux rows below the bottom of the visible screen (&lt;em&gt;vertical blanking&lt;/em&gt;), and the &lt;span class="caps"&gt;CPU&lt;/span&gt; can again do whatever it wants.  Eventually it returns to the top-left corner to draw again, concluding a single frame.  The entire process happens 59.7 times per&amp;nbsp;second.&lt;/p&gt;
&lt;p&gt;There&amp;#8217;s one exception: &lt;span class="caps"&gt;DMA&lt;/span&gt; transfers can happen any time, &lt;em&gt;but&lt;/em&gt; the &lt;span class="caps"&gt;LCD&lt;/span&gt; will simply not draw sprites during the&amp;nbsp;transfer.&lt;/p&gt;
&lt;p&gt;So I &lt;em&gt;probably shouldn&amp;#8217;t&lt;/em&gt; be writing to tiles and palettes willy-nilly.  I &lt;em&gt;suspect&lt;/em&gt; I got away with it because it happened in that first &lt;span class="caps"&gt;OAM&lt;/span&gt;-searching stage…  and/or because I did it on emulators which are a bit more flexible than the original&amp;nbsp;hardware.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;In&amp;nbsp;fact&amp;#8230;&lt;/em&gt;&lt;/p&gt;
&lt;div class="prose-full-illustration"&gt;
&lt;img src="https://eev.ee/media/cheezball/01c-first-frame.png" alt="Same screenshot as above, but the first row of pixels is corrupt"&gt;
&lt;/div&gt;

&lt;p&gt;I took this screenshot by loading the &lt;span class="caps"&gt;ROM&lt;/span&gt; I have so far, pausing it, resetting it, and then advancing a single frame.  This is the very first frame my game shows.  If you look closely at the first row of pixels, you can see they&amp;#8217;re actually corrupt — they&amp;#8217;re being drawn before I&amp;#8217;ve set up the palette!  You can even see &lt;em&gt;each palette entry&lt;/em&gt; taking effect along the&amp;nbsp;row.&lt;/p&gt;
&lt;p&gt;This is &lt;em&gt;very cool&lt;/em&gt;.  It also means my current code &lt;em&gt;would not work at all&lt;/em&gt; on actual hardware.  I should probably just turn the screen off while I&amp;#8217;m doing setup like&amp;nbsp;this.&lt;/p&gt;
&lt;p&gt;It&amp;#8217;s interesting that only &lt;span class="caps"&gt;OAM&lt;/span&gt; gets a special workaround in the form of a &lt;span class="caps"&gt;DMA&lt;/span&gt; transfer — I imagine because sprites move around much more often than the tileset changes — but having the &lt;span class="caps"&gt;LCD&lt;/span&gt; stop drawing sprites in the meantime is quite a limitation.  Surely, you&amp;#8217;d only want to do a &lt;span class="caps"&gt;DMA&lt;/span&gt; transfer during vblank anyway?  It &lt;em&gt;is&lt;/em&gt; much faster than copying by hand, so I&amp;#8217;ll still take&amp;nbsp;it.&lt;/p&gt;
&lt;p&gt;All of this is to say: I&amp;#8217;m gonna need to care about&amp;nbsp;vblanks.&lt;/p&gt;
&lt;hr /&gt;
&lt;p&gt;&lt;em&gt;Incidentally&lt;/em&gt;, the presence of hblank is &lt;em&gt;very cool&lt;/em&gt; and can be used for a number of neat effects, especially when combined with the Game Boy&amp;#8217;s ability to call back into user code when the &lt;span class="caps"&gt;LCD&lt;/span&gt; reaches a specific &lt;em&gt;row&lt;/em&gt;:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;The &lt;span class="caps"&gt;GBC&lt;/span&gt; Zelda games use it for map scrolling.  The status bar at the top is in one of the two background maps, and as soon as that finishes drawing, the game switches to the other one, which contains the&amp;nbsp;world.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Those same games also use it for a horizontal wavy effect, both when warping around and when underwater — all they need to do is change the background layer&amp;#8217;s x offset during each&amp;nbsp;hblank!&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;The wiki points out that &lt;span class="caps"&gt;OAM&lt;/span&gt; could be written to &lt;em&gt;in the middle of&lt;/em&gt; a screen update, thus bypassing the 40-object restriction: draw 40 objects on the top half of the screen, swap out &lt;span class="caps"&gt;OAM&lt;/span&gt; midway, and then the &lt;span class="caps"&gt;LCD&lt;/span&gt; will draw a different 40 on the bottom&amp;nbsp;half!&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;I imagine you could also change palettes midway through a redraw and exceed the usual limit of 56 colors on screen at a time!  No telling whether this sort of trick would work on an emulator,&amp;nbsp;though.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;I am &lt;em&gt;very excited&lt;/em&gt; at the prospects&amp;nbsp;here.&lt;/p&gt;
&lt;p&gt;I&amp;#8217;m also slightly terrified.  I have a fixed amount of time between frames, and with the &lt;span class="caps"&gt;LCD&lt;/span&gt; as separate hardware, there&amp;#8217;s no such thing as a slow frame.  If I don&amp;#8217;t finish, things go bad.  And that time is measured &lt;em&gt;in instructions&lt;/em&gt; — an &lt;code&gt;ld&lt;/code&gt; always takes the same number of cycles!  There&amp;#8217;s no faster computer or reducing &lt;span class="caps"&gt;GC&lt;/span&gt; pressure.  There&amp;#8217;s just me.&amp;nbsp;Yikes.&lt;/p&gt;
&lt;h2 id="back-to-drawing-a-sprite"&gt;&lt;a class="toclink" href="#back-to-drawing-a-sprite"&gt;Back to drawing a&amp;nbsp;sprite&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;I haven&amp;#8217;t had a single new screenshot this entire post!  This is ridiculous.  All I want is to draw a thing to the&amp;nbsp;screen.&lt;/p&gt;
&lt;p&gt;I have some data in my &lt;span class="caps"&gt;OAM&lt;/span&gt; buffer.  I have &lt;span class="caps"&gt;DMA&lt;/span&gt; set up.  All I &lt;em&gt;should&lt;/em&gt; need to do now is start a&amp;nbsp;transfer.&lt;/p&gt;
&lt;table class="highlighttable"&gt;&lt;tr&gt;&lt;td class="linenos"&gt;&lt;div class="linenodiv"&gt;&lt;pre&gt;&lt;span class="normal"&gt;1&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/td&gt;&lt;td class="code"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;    &lt;span class="ow"&gt;call&lt;/span&gt; &lt;span class="mh"&gt;$ff80&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;p&gt;And…  nothing.  mGBA&amp;#8217;s memory viewer confirms everything&amp;#8217;s in the right place, but nothing&amp;#8217;s on the&amp;nbsp;screen.&lt;/p&gt;
&lt;p&gt;Whoops!  Remember that &lt;span class="caps"&gt;LCD&lt;/span&gt; controller register, and how it defaults to $91?  Well, bit 1 is whether to show objects &lt;em&gt;at all&lt;/em&gt;, and it defaults to &lt;em&gt;off&lt;/em&gt;.  So let&amp;#8217;s fix&amp;nbsp;that.&lt;/p&gt;
&lt;table class="highlighttable"&gt;&lt;tr&gt;&lt;td class="linenos"&gt;&lt;div class="linenodiv"&gt;&lt;pre&gt;&lt;span class="normal"&gt;1&lt;/span&gt;
&lt;span class="normal"&gt;2&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/td&gt;&lt;td class="code"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;    &lt;span class="ow"&gt;ld&lt;/span&gt; &lt;span class="nb"&gt;a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mb"&gt;%10010011&lt;/span&gt;  &lt;span class="c1"&gt;; $91 plus bit 2&lt;/span&gt;
    &lt;span class="ow"&gt;ld&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mh"&gt;$ff40&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="nb"&gt;a&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;div class="prose-full-illustration"&gt;
&lt;img src="https://eev.ee/media/cheezball/01d-first-object.png" alt="The same gaudy background, but now with a partial Anise sprite on top"&gt;
&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;&lt;em&gt;&lt;span class="caps"&gt;SUCCESS&lt;/span&gt;!&lt;/em&gt;&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;It doesn&amp;#8217;t look like much, but it took a &lt;em&gt;lot&lt;/em&gt; of flailing to get here, and I was &lt;a href="http://rampantgames.com/blog/?p=7745"&gt;overjoyed when I first saw it&lt;/a&gt;.  The rest should be a breeze!&amp;nbsp;Right?&lt;/p&gt;
&lt;h2 id="to-be-continued"&gt;&lt;a class="toclink" href="#to-be-continued"&gt;To be&amp;nbsp;continued&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;That doesn&amp;#8217;t even get us all the way through &lt;a href="https://github.com/eevee/anise-cheezball-rising/commit/1b17c709f44718983015539ceb8709fb5ed8edbd"&gt;commit &lt;code&gt;1b17c7&lt;/code&gt;&lt;/a&gt;, but this is already more than&amp;nbsp;enough.&lt;/p&gt;
&lt;p&gt;Next time: &lt;a href="https://eev.ee/blog/2018/07/05/cheezball-rising-main-loop-input-and-a-game/"&gt;input, and moderately less eye-searing art&lt;/a&gt;!&lt;/p&gt;</content><category term="blog"></category><category term="tech"></category><category term="gamedev"></category><category term="cheezball rising"></category></entry><entry><title>Cheezball Rising: A new Game Boy Color game</title><link href="https://eev.ee/blog/2018/06/19/cheezball-rising-a-new-game-boy-color-game/" rel="alternate"></link><published>2018-06-19T18:49:00-07:00</published><updated>2018-06-19T18:49:00-07:00</updated><author><name>Eevee</name></author><id>tag:eev.ee,2018-06-19:/blog/2018/06/19/cheezball-rising-a-new-game-boy-color-game/</id><summary type="html">&lt;p&gt;This is a series about &lt;a href="https://github.com/eevee/anise-cheezball-rising"&gt;&lt;strong&gt;Star Anise Chronicles: Cheezball Rising&lt;/strong&gt;&lt;/a&gt;, an expansive adventure game about my cat for the Game Boy Color.  Follow along as I struggle to make something with this bleeding-edge console!&lt;/p&gt;
&lt;p&gt;GitHub has &lt;a href="https://github.com/eevee/anise-cheezball-rising/releases"&gt;intermittent prebuilt ROMs&lt;/a&gt;, or you can get them a week early &lt;a href="https://www.patreon.com/eevee/posts?tag=cheezball%20rising"&gt;on Patreon&lt;/a&gt; if you pledge $4.  More details in the &lt;a href="https://github.com/eevee/anise-cheezball-rising"&gt;&lt;span class="caps"&gt;README&lt;/span&gt;&lt;/a&gt;!&lt;/p&gt;
&lt;hr/&gt;
&lt;p&gt;In this issue, I figure out how to put literally anything on the goddamn screen, then add a splash of color.&lt;/p&gt;
&lt;p&gt;Next: &lt;a href="https://eev.ee/blog/2018/06/21/cheezball-rising-drawing-a-sprite/"&gt;drawing a sprite&lt;/a&gt;.&lt;/p&gt;
</summary><content type="html">&lt;p&gt;This is a series about &lt;a href="https://github.com/eevee/anise-cheezball-rising"&gt;&lt;strong&gt;Star Anise Chronicles: Cheezball Rising&lt;/strong&gt;&lt;/a&gt;, an expansive adventure game about my cat for the Game Boy Color.  Follow along as I struggle to make something with this bleeding-edge&amp;nbsp;console!&lt;/p&gt;
&lt;p&gt;GitHub has &lt;a href="https://github.com/eevee/anise-cheezball-rising/releases"&gt;intermittent prebuilt ROMs&lt;/a&gt;, or you can get them a week early &lt;a href="https://www.patreon.com/eevee/posts?tag=cheezball%20rising"&gt;on Patreon&lt;/a&gt; if you pledge $4.  More details in the &lt;a href="https://github.com/eevee/anise-cheezball-rising"&gt;&lt;span class="caps"&gt;README&lt;/span&gt;&lt;/a&gt;!&lt;/p&gt;
&lt;hr /&gt;
&lt;p&gt;In this issue, I figure out how to put literally anything on the goddamn screen, then add a splash of&amp;nbsp;color.&lt;/p&gt;
&lt;p&gt;Next: &lt;a href="https://eev.ee/blog/2018/06/21/cheezball-rising-drawing-a-sprite/"&gt;drawing a sprite&lt;/a&gt;.&lt;/p&gt;


&lt;h2 id="the-plan"&gt;&lt;a class="toclink" href="#the-plan"&gt;The&amp;nbsp;plan&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;I&amp;#8217;m making a Game Boy Color&amp;nbsp;game!&lt;/p&gt;
&lt;p&gt;I have &lt;em&gt;no&lt;/em&gt;— okay, &lt;em&gt;not much&lt;/em&gt; idea what I&amp;#8217;m doing, so I&amp;#8217;m going to &lt;strong&gt;document my progress&lt;/strong&gt; as I try to forge a 90s handheld game out of&amp;nbsp;nothing.&lt;/p&gt;
&lt;p&gt;I do usually try to keep tech stuff accessible, but this is going to get so arcane that that might be a fool&amp;#8217;s errand.  Think of this as less of an extended tutorial, more of a long-form&amp;nbsp;Twitter.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Also&lt;/em&gt;, I&amp;#8217;ll be posting regular builds on &lt;a href="https://www.patreon.com/eevee"&gt;Patreon&lt;/a&gt; for $4 supporters, which will be available a week later for everyone else.  I imagine they&amp;#8217;ll generally stay in lockstep with the posts, unless I fall behind on the writing part.  But when has &lt;em&gt;that&lt;/em&gt; ever&amp;nbsp;happened?&lt;/p&gt;
&lt;p&gt;Your very own gamedev legend is about to unfold!  A world of dreams and adventures with gbz80 assembly awaits!  &lt;em&gt;Let&amp;#8217;s&amp;nbsp;go!&lt;/em&gt;&lt;/p&gt;
&lt;h2 id="prerequisites"&gt;&lt;a class="toclink" href="#prerequisites"&gt;Prerequisites&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;First things first.  I have a &lt;a href="https://eev.ee/blog/2017/08/02/datamining-pokemon/"&gt;teeny bit of experience with Game Boy hacking&lt;/a&gt;, so I know I&amp;nbsp;need:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;An emulator&lt;/strong&gt;.  I have no way to run arbitrary code on an actual Game Boy Color, after all.  I like &lt;a href="https://mgba.io/"&gt;mGBA&lt;/a&gt;, which strives for accuracy and has some debug tools built&amp;nbsp;in.&lt;/p&gt;
&lt;p&gt;There&amp;#8217;s already a serious pitfall here: emulators are generally designed to run games that would work correctly on the actual hardware, but they won&amp;#8217;t necessarily &lt;em&gt;reject&lt;/em&gt; games that &lt;em&gt;wouldn&amp;#8217;t&lt;/em&gt; work on actual hardware.  In other words, something that works in an emulator might still not work on a real &lt;span class="caps"&gt;GBC&lt;/span&gt;.  I would of course prefer that this game work on the actual console it&amp;#8217;s built for, but I&amp;#8217;ll worry about that&amp;nbsp;later.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;An assembler&lt;/strong&gt;, which can build Game Boy assembly code into a &lt;span class="caps"&gt;ROM&lt;/span&gt;.  I pretty much wrote one of these myself already for the Pokémon shenanigans, but let&amp;#8217;s go with something a little more robust here.  I&amp;#8217;m using &lt;a href="https://github.com/rednex/rgbds"&gt;&lt;span class="caps"&gt;RGBDS&lt;/span&gt;&lt;/a&gt;, which has a couple nice features like macros and a separate linking step.  It compiles super easily,&amp;nbsp;too.&lt;/p&gt;
&lt;p&gt;I also hunted down a vim syntax file, uh, somewhere.  I can&amp;#8217;t remember which one it was now, and it&amp;#8217;s kind of glitchy&amp;nbsp;anyway.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Some documentation&lt;/strong&gt;.  I don&amp;#8217;t know exactly how this surfaced, but the &lt;em&gt;actual official&lt;/em&gt; &lt;a href="https://archive.org/download/GameBoyProgManVer1.1/GameBoyProgManVer1.1.pdf"&gt;Game Boy programming manual&lt;/a&gt; is on archive.org.  It glosses over some things and assumes some existing low-level knowledge, but for the most part it&amp;#8217;s a very solid&amp;nbsp;reference.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;For everything else, there&amp;#8217;s Google, and also the curated &lt;a href="https://github.com/avivace/awesome-gbdev"&gt;awesome-gbdev&lt;/a&gt; list of&amp;nbsp;resources.&lt;/p&gt;
&lt;p&gt;That list includes several &lt;a href="https://github.com/avivace/awesome-gbdev#boilerplates"&gt;skeleton projects&lt;/a&gt; for getting started, but I&amp;#8217;m not going to use them.  I want to be able to account for every byte of whatever I create.  I &lt;em&gt;will&lt;/em&gt;, however, refer to them if I get stuck early on.  (Spoilers: I get stuck early&amp;nbsp;on.)&lt;/p&gt;
&lt;p&gt;And that&amp;#8217;s it!  The rest is up to&amp;nbsp;me.&lt;/p&gt;
&lt;h2 id="making-nothing-from-nothing"&gt;&lt;a class="toclink" href="#making-nothing-from-nothing"&gt;Making nothing from&amp;nbsp;nothing&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Might as well start with a Makefile.  The &lt;a href="https://rednex.github.io/rgbds/rgbds.7.html"&gt;rgbds root documentation&lt;/a&gt; leads me to the following&amp;nbsp;incantation:&lt;/p&gt;
&lt;table class="highlighttable"&gt;&lt;tr&gt;&lt;td class="linenos"&gt;&lt;div class="linenodiv"&gt;&lt;pre&gt;&lt;span class="normal"&gt;1&lt;/span&gt;
&lt;span class="normal"&gt;2&lt;/span&gt;
&lt;span class="normal"&gt;3&lt;/span&gt;
&lt;span class="normal"&gt;4&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/td&gt;&lt;td class="code"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="nf"&gt;all&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;
        rgbasm -o main.o main.rgbasm
        rgblink -o gamegirl.gb main.o
        rgbfix -v -p &lt;span class="m"&gt;0&lt;/span&gt; gamegirl.gb
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;p&gt;(I, uh, named this project &amp;#8220;gamegirl&amp;#8221; before I figured out what it was going to be.  It&amp;#8217;s a sort of witticism, you&amp;nbsp;see.)&lt;/p&gt;
&lt;p&gt;This works basically like every C compiler under the sun, as you might expect: every source file compiles to an object file, then a linker bundles all the object files into a &lt;span class="caps"&gt;ROM&lt;/span&gt;.  If I only change one source file, I only have to rebuild one object&amp;nbsp;file.&lt;/p&gt;
&lt;p&gt;Of course, this Makefile is terrible garbage and will rebuild the entire project unconditionally every time, but at the moment that takes a fraction of a second so I don&amp;#8217;t&amp;nbsp;care.&lt;/p&gt;
&lt;p&gt;The extra &lt;code&gt;rgbfix&lt;/code&gt; step is new, though — it adds the Nintendo logo (the one you see when you start up a Game Boy) to the header at the beginning of the &lt;span class="caps"&gt;ROM&lt;/span&gt;.  Without this, the console will assume the cartridge is dirty or missing or otherwise unreadable, and will refuse to do anything at all.  (I &lt;em&gt;could&lt;/em&gt; also bake the logo into the source itself, but given that it&amp;#8217;s just a fixed block of bytes and &lt;code&gt;rgbfix&lt;/code&gt; is bundled with the assembler, I see no reason to bother with&amp;nbsp;that.)&lt;/p&gt;
&lt;p&gt;All I need now is a source file, &lt;code&gt;main.rgbasm&lt;/code&gt;, which I populate&amp;nbsp;with:&lt;/p&gt;
&lt;table class="highlighttable"&gt;&lt;tr&gt;&lt;td class="linenos"&gt;&lt;div class="linenodiv"&gt;&lt;pre&gt;&lt;span class="normal"&gt;1&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/td&gt;&lt;td class="code"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;p&gt;Nothing!  I don&amp;#8217;t know what I expect from this, but I&amp;#8217;m curious to see what comes out.  And what comes out is a working &lt;span class="caps"&gt;ROM&lt;/span&gt;!&lt;/p&gt;
&lt;div class="prose-full-illustration"&gt;
&lt;img src="https://eev.ee/media/cheezball/00a-nothing.png" alt="A completely blank screen"&gt;
&lt;/div&gt;

&lt;p&gt;Maybe &amp;#8220;working&amp;#8221; is a strong choice of word, given that it doesn&amp;#8217;t actually &lt;em&gt;do&lt;/em&gt;&amp;nbsp;anything.&lt;/p&gt;
&lt;h2 id="doing-something"&gt;&lt;a class="toclink" href="#doing-something"&gt;Doing&amp;nbsp;something&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;It would be fantastic to put something on the screen.  This turned out to be harder than&amp;nbsp;expected.&lt;/p&gt;
&lt;p&gt;First attempt.  I know that the Game Boy starts running code at $0150, immediately after the end of the header.  So I&amp;#8217;ll put some code&amp;nbsp;there.&lt;/p&gt;
&lt;p&gt;A brief Game Boy graphics primer: there are two layers, the &lt;em&gt;background&lt;/em&gt; and &lt;em&gt;objects&lt;/em&gt;.  (There&amp;#8217;s also a third layer, the &lt;em&gt;window&lt;/em&gt;, which I don&amp;#8217;t entirely understand yet.)  The background is a grid of 8×8 tiles, two bits per pixel, for a total of four shades of gray.  Objects can move around freely, but they lose color 0 to transparency, so they can only use three&amp;nbsp;colors.&lt;/p&gt;
&lt;p&gt;There are lots more interesting details and restrictions, which I will think about more&amp;nbsp;later.&lt;/p&gt;
&lt;p&gt;Drawing objects is complicated, and all I want to do right now is get &lt;em&gt;something&lt;/em&gt;.  I&amp;#8217;m pretty sure the background defaults to showing all tile 0, so I&amp;#8217;ll try replacing tile 0 with a gradient and see what&amp;nbsp;happens.&lt;/p&gt;
&lt;p&gt;Tiles are 8×8 and two bits per pixel, which means each row takes two bytes, and the whole tile is 16 bytes.  Tiles are defined in one big contiguous block starting at $8000 — or, maybe $8800, sometimes — so all I need to do&amp;nbsp;is:&lt;/p&gt;
&lt;table class="highlighttable"&gt;&lt;tr&gt;&lt;td class="linenos"&gt;&lt;div class="linenodiv"&gt;&lt;pre&gt;&lt;span class="normal"&gt; 1&lt;/span&gt;
&lt;span class="normal"&gt; 2&lt;/span&gt;
&lt;span class="normal"&gt; 3&lt;/span&gt;
&lt;span class="normal"&gt; 4&lt;/span&gt;
&lt;span class="normal"&gt; 5&lt;/span&gt;
&lt;span class="normal"&gt; 6&lt;/span&gt;
&lt;span class="normal"&gt; 7&lt;/span&gt;
&lt;span class="normal"&gt; 8&lt;/span&gt;
&lt;span class="normal"&gt; 9&lt;/span&gt;
&lt;span class="normal"&gt;10&lt;/span&gt;
&lt;span class="normal"&gt;11&lt;/span&gt;
&lt;span class="normal"&gt;12&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/td&gt;&lt;td class="code"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="k"&gt;SECTION &amp;quot;main&amp;quot;, ROM0[$0150]&lt;/span&gt;
    &lt;span class="ow"&gt;ld&lt;/span&gt; &lt;span class="nb"&gt;hl&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mh"&gt;$8000&lt;/span&gt;
    &lt;span class="ow"&gt;ld&lt;/span&gt; &lt;span class="nb"&gt;a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mb"&gt;%00011011&lt;/span&gt;
    &lt;span class="k"&gt;REPT 16&lt;/span&gt;
    &lt;span class="ow"&gt;ld&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;hl&lt;/span&gt;&lt;span class="p"&gt;+],&lt;/span&gt; &lt;span class="nb"&gt;a&lt;/span&gt;
    &lt;span class="k"&gt;ENDR&lt;/span&gt;

&lt;span class="nf"&gt;_halt:&lt;/span&gt;
    &lt;span class="c1"&gt;; Do nothing, forever&lt;/span&gt;
    &lt;span class="ow"&gt;halt&lt;/span&gt;
    &lt;span class="ow"&gt;nop&lt;/span&gt;
    &lt;span class="ow"&gt;jr&lt;/span&gt; &lt;span class="nv"&gt;_halt&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;hr /&gt;
&lt;p&gt;If you are not familiar with assembly, this series is going to be a wild ride.  But here&amp;#8217;s a very very brief&amp;nbsp;primer.&lt;/p&gt;
&lt;p&gt;Assembly language — really, &lt;em&gt;an&lt;/em&gt; assembly language — is little more than a set of human-readable names for the primitive operations a &lt;span class="caps"&gt;CPU&lt;/span&gt; knows how to do.  And those operations, by and large, consist of moving bytes around.  The names tend to be very short, because you end up typing them &lt;strong&gt;a lot&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;Most of the work is done in &lt;em&gt;registers&lt;/em&gt;, which are a handful of spaces for storing bytes right on the &lt;span class="caps"&gt;CPU&lt;/span&gt;.  At this level, &lt;span class="caps"&gt;RAM&lt;/span&gt; is relatively &lt;em&gt;slow&lt;/em&gt; — it&amp;#8217;s further away, outside the chip — so you want to do as much work as possible in registers.  Indeed, most operations can only be done &lt;em&gt;on&lt;/em&gt; registers, so there&amp;#8217;s a lot of fetching stuff from &lt;span class="caps"&gt;RAM&lt;/span&gt; and operating on it and then putting it back in &lt;span class="caps"&gt;RAM&lt;/span&gt;.&lt;/p&gt;
&lt;p&gt;The Game Boy &lt;span class="caps"&gt;CPU&lt;/span&gt;, a modified Z80, has eight byte-sized registers.  They&amp;#8217;re often referred to in pairs, because they can be paired up to make a 16-bit values (giving you access to a full &lt;span class="caps"&gt;64KB&lt;/span&gt; address space).  And they are: &lt;code&gt;af&lt;/code&gt;, &lt;code&gt;bc&lt;/code&gt;, &lt;code&gt;de&lt;/code&gt;, &lt;code&gt;hl&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;The &lt;code&gt;af&lt;/code&gt; pair is special.  The &lt;code&gt;f&lt;/code&gt; register is used for &lt;em&gt;flags&lt;/em&gt;, such as whether the last instruction caused an overflow, so it&amp;#8217;s not generally touched directly.  The &lt;code&gt;a&lt;/code&gt; register is called the &lt;em&gt;accumulator&lt;/em&gt; and is most commonly used for math operations — in fact, a lot of math operations can &lt;em&gt;only&lt;/em&gt; be done on &lt;code&gt;a&lt;/code&gt;.  The &lt;code&gt;hl&lt;/code&gt; register is most often used for addresses, and there are a couple instructions specific to &lt;code&gt;hl&lt;/code&gt; that are convenient for memory access.  (The &lt;code&gt;h&lt;/code&gt; and &lt;code&gt;l&lt;/code&gt; even refer to the &lt;em&gt;high&lt;/em&gt; and &lt;em&gt;low&lt;/em&gt; byte of an address.)  The other two pairs aren&amp;#8217;t especially&amp;nbsp;noteworthy.&lt;/p&gt;
&lt;p&gt;Also!  Not every address is actually &lt;span class="caps"&gt;RAM&lt;/span&gt;; the address space ($0000 through $ffff) is carved into several distinct areas, which we will see as I go along.  $8000 is the beginning of display &lt;span class="caps"&gt;RAM&lt;/span&gt;, which the screen reads from asynchronously.  Also, a lot of addresses above $ff00 (also called &amp;#8220;registers&amp;#8221;) are special and control hardware in some way, or even perform some action when written&amp;nbsp;to.&lt;/p&gt;
&lt;p&gt;With that in mind, here&amp;#8217;s the above code with explanatory&amp;nbsp;comments:&lt;/p&gt;
&lt;table class="highlighttable"&gt;&lt;tr&gt;&lt;td class="linenos"&gt;&lt;div class="linenodiv"&gt;&lt;pre&gt;&lt;span class="normal"&gt; 1&lt;/span&gt;
&lt;span class="normal"&gt; 2&lt;/span&gt;
&lt;span class="normal"&gt; 3&lt;/span&gt;
&lt;span class="normal"&gt; 4&lt;/span&gt;
&lt;span class="normal"&gt; 5&lt;/span&gt;
&lt;span class="normal"&gt; 6&lt;/span&gt;
&lt;span class="normal"&gt; 7&lt;/span&gt;
&lt;span class="normal"&gt; 8&lt;/span&gt;
&lt;span class="normal"&gt; 9&lt;/span&gt;
&lt;span class="normal"&gt;10&lt;/span&gt;
&lt;span class="normal"&gt;11&lt;/span&gt;
&lt;span class="normal"&gt;12&lt;/span&gt;
&lt;span class="normal"&gt;13&lt;/span&gt;
&lt;span class="normal"&gt;14&lt;/span&gt;
&lt;span class="normal"&gt;15&lt;/span&gt;
&lt;span class="normal"&gt;16&lt;/span&gt;
&lt;span class="normal"&gt;17&lt;/span&gt;
&lt;span class="normal"&gt;18&lt;/span&gt;
&lt;span class="normal"&gt;19&lt;/span&gt;
&lt;span class="normal"&gt;20&lt;/span&gt;
&lt;span class="normal"&gt;21&lt;/span&gt;
&lt;span class="normal"&gt;22&lt;/span&gt;
&lt;span class="normal"&gt;23&lt;/span&gt;
&lt;span class="normal"&gt;24&lt;/span&gt;
&lt;span class="normal"&gt;25&lt;/span&gt;
&lt;span class="normal"&gt;26&lt;/span&gt;
&lt;span class="normal"&gt;27&lt;/span&gt;
&lt;span class="normal"&gt;28&lt;/span&gt;
&lt;span class="normal"&gt;29&lt;/span&gt;
&lt;span class="normal"&gt;30&lt;/span&gt;
&lt;span class="normal"&gt;31&lt;/span&gt;
&lt;span class="normal"&gt;32&lt;/span&gt;
&lt;span class="normal"&gt;33&lt;/span&gt;
&lt;span class="normal"&gt;34&lt;/span&gt;
&lt;span class="normal"&gt;35&lt;/span&gt;
&lt;span class="normal"&gt;36&lt;/span&gt;
&lt;span class="normal"&gt;37&lt;/span&gt;
&lt;span class="normal"&gt;38&lt;/span&gt;
&lt;span class="normal"&gt;39&lt;/span&gt;
&lt;span class="normal"&gt;40&lt;/span&gt;
&lt;span class="normal"&gt;41&lt;/span&gt;
&lt;span class="normal"&gt;42&lt;/span&gt;
&lt;span class="normal"&gt;43&lt;/span&gt;
&lt;span class="normal"&gt;44&lt;/span&gt;
&lt;span class="normal"&gt;45&lt;/span&gt;
&lt;span class="normal"&gt;46&lt;/span&gt;
&lt;span class="normal"&gt;47&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/td&gt;&lt;td class="code"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="c1"&gt;; This is a directive for the assembler to put the following&lt;/span&gt;
&lt;span class="c1"&gt;; code at $0150 in the final ROM.&lt;/span&gt;
&lt;span class="k"&gt;SECTION &amp;quot;main&amp;quot;, ROM0[$0150]&lt;/span&gt;
    &lt;span class="c1"&gt;; Put the hex value $8000 into registers hl.  Really, that&lt;/span&gt;
    &lt;span class="c1"&gt;; means put $80 into h and $00 into l.&lt;/span&gt;
    &lt;span class="ow"&gt;ld&lt;/span&gt; &lt;span class="nb"&gt;hl&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mh"&gt;$8000&lt;/span&gt;

    &lt;span class="c1"&gt;; Put this binary value into registers a.&lt;/span&gt;
    &lt;span class="c1"&gt;; It&amp;#39;s just 0 1 2 3, a color gradient.&lt;/span&gt;
    &lt;span class="ow"&gt;ld&lt;/span&gt; &lt;span class="nb"&gt;a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mb"&gt;%00011011&lt;/span&gt;

    &lt;span class="c1"&gt;; This is actually a macro this particular assembler&lt;/span&gt;
    &lt;span class="c1"&gt;; understands, which will repeat the following code 16&lt;/span&gt;
    &lt;span class="c1"&gt;; times, exactly as if I&amp;#39;d copy-pasted it.&lt;/span&gt;
    &lt;span class="k"&gt;REPT 16&lt;/span&gt;

    &lt;span class="c1"&gt;; The brackets (sometimes written as parens) mean to use hl&lt;/span&gt;
    &lt;span class="c1"&gt;; as a position in RAM, rather than operating on hl itself.&lt;/span&gt;
    &lt;span class="c1"&gt;; So this copies a into the position in RAM given by&lt;/span&gt;
    &lt;span class="c1"&gt;; hl (initially $8000), and the + adds 1 to hl afterwards.&lt;/span&gt;
    &lt;span class="c1"&gt;; This is one reason hl is nice for storing addresses: the +&lt;/span&gt;
    &lt;span class="c1"&gt;; variant is handy for writing a sequence of bytes to RAM,&lt;/span&gt;
    &lt;span class="c1"&gt;; and it only exists for hl.&lt;/span&gt;
    &lt;span class="ow"&gt;ld&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;hl&lt;/span&gt;&lt;span class="p"&gt;+],&lt;/span&gt; &lt;span class="nb"&gt;a&lt;/span&gt;

    &lt;span class="c1"&gt;; End the REPT block&lt;/span&gt;
    &lt;span class="k"&gt;ENDR&lt;/span&gt;

&lt;span class="c1"&gt;; This is a label, used to refer to some position in the code.&lt;/span&gt;
&lt;span class="c1"&gt;; It only exists in the source file.&lt;/span&gt;
&lt;span class="nf"&gt;_halt:&lt;/span&gt;
    &lt;span class="c1"&gt;; Stop all CPU activity until there&amp;#39;s an interrupt.  I&lt;/span&gt;
    &lt;span class="c1"&gt;; haven&amp;#39;t turned any interrupts on, so this stops forever.&lt;/span&gt;
    &lt;span class="ow"&gt;halt&lt;/span&gt;

    &lt;span class="c1"&gt;; The Game Boy hardware has a bug where, under rare and&lt;/span&gt;
    &lt;span class="c1"&gt;; unspecified conditions, the instruction after a halt will&lt;/span&gt;
    &lt;span class="c1"&gt;; be skipped.  So every halt should be followed by a nop,&lt;/span&gt;
    &lt;span class="c1"&gt;; &amp;quot;no operation&amp;quot;, which does nothing.&lt;/span&gt;
    &lt;span class="ow"&gt;nop&lt;/span&gt;

    &lt;span class="c1"&gt;; This jumps back up to the label.  It&amp;#39;s short for &amp;quot;jump&lt;/span&gt;
    &lt;span class="c1"&gt;; relative&amp;quot;, and will end up as an instruction saying&lt;/span&gt;
    &lt;span class="c1"&gt;; something like &amp;quot;jump backwards five bytes&amp;quot;, or however far&lt;/span&gt;
    &lt;span class="c1"&gt;; back _halt is.  (Different instructions can be different&lt;/span&gt;
    &lt;span class="c1"&gt;; lengths.)&lt;/span&gt;
    &lt;span class="ow"&gt;jr&lt;/span&gt; &lt;span class="nv"&gt;_halt&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;p&gt;Okay!  Glad you&amp;#8217;re all caught up.  The rgbds documentation includes &lt;a href="https://rednex.github.io/rgbds/gbz80.7.html"&gt;a list of all the available operations&lt;/a&gt; (as well as &lt;a href="https://rednex.github.io/rgbds/rgbasm.5.html"&gt;assembler syntax&lt;/a&gt;), and once you get used to the short names, I also like this &lt;a href="http://www.pastraiser.com/cpu/gameboy/gameboy_opcodes.html"&gt;very compact chart&lt;/a&gt; of all the instructions and how they compile to machine code.  (Note that that chart spells &lt;code&gt;[hl+]&lt;/code&gt; as &lt;code&gt;(HLI)&lt;/code&gt;, for &amp;#8220;increment&amp;#8221; — the human-readable names are somewhat arbitrary and can sometimes vary between&amp;nbsp;assemblers.)&lt;/p&gt;
&lt;p&gt;Now, let&amp;#8217;s see what this&amp;nbsp;does!&lt;/p&gt;
&lt;hr /&gt;
&lt;div class="prose-full-illustration"&gt;
&lt;img src="https://eev.ee/media/cheezball/00a-nothing.png" alt="A completely blank screen, still"&gt;
&lt;/div&gt;

&lt;p&gt;Wow!  It&amp;#8217;s…  still nothing.  Hang&amp;nbsp;on.&lt;/p&gt;
&lt;p&gt;If I open the debugger and hit Break, I find out that the &lt;span class="caps"&gt;CPU&lt;/span&gt; is at address $0120 — &lt;em&gt;before&lt;/em&gt; my code — and is on an instruction &lt;code&gt;DD&lt;/code&gt;.  What&amp;#8217;s &lt;code&gt;DD&lt;/code&gt;?  Well, according to &lt;a href="http://www.pastraiser.com/cpu/gameboy/gameboy_opcodes.html"&gt;this convenient chart&lt;/a&gt;, it&amp;#8217;s…  nothing.  That&amp;#8217;s not an&amp;nbsp;instruction.&lt;/p&gt;
&lt;p&gt;Hmm.&lt;/p&gt;
&lt;h2 id="problem-solving"&gt;&lt;a class="toclink" href="#problem-solving"&gt;Problem&amp;nbsp;solving&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Maybe it&amp;#8217;s time to look at one of those skeleton projects after all.  I crack open the smallest one, &lt;a href="https://github.com/exezin/gb-template"&gt;gb-template&lt;/a&gt;, and it &lt;em&gt;seems&lt;/em&gt; to be doing the same thing: its code &lt;a href="https://github.com/exezin/gb-template/blob/1d05f41126289d5970e9fe6993aed6cea8503c7c/src/bank0.asm#L16"&gt;starts at $0150&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;It takes me a bit to realize my mistake here.  Practically every Game Boy game starts its code at $0150, but that&amp;#8217;s not what the actual hardware specifies.  The real start point is $0100, which is immediately &lt;em&gt;before&lt;/em&gt; the header!  There are only four bytes before the header, just enough for…  a jump&amp;nbsp;instruction.&lt;/p&gt;
&lt;p&gt;Okay!  No&amp;nbsp;problem.&lt;/p&gt;
&lt;table class="highlighttable"&gt;&lt;tr&gt;&lt;td class="linenos"&gt;&lt;div class="linenodiv"&gt;&lt;pre&gt;&lt;span class="normal"&gt;1&lt;/span&gt;
&lt;span class="normal"&gt;2&lt;/span&gt;
&lt;span class="normal"&gt;3&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/td&gt;&lt;td class="code"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="k"&gt;SECTION &amp;quot;entry point&amp;quot;, ROM0[$0100]&lt;/span&gt;
    &lt;span class="ow"&gt;nop&lt;/span&gt;
    &lt;span class="ow"&gt;jp&lt;/span&gt; &lt;span class="mh"&gt;$0150&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;p&gt;Why the &lt;code&gt;nop&lt;/code&gt;?  I have no idea, but &lt;em&gt;all&lt;/em&gt; of these boilerplate projects do&amp;nbsp;it.&lt;/p&gt;
&lt;div class="prose-full-illustration"&gt;
&lt;img src="https://eev.ee/media/cheezball/00b-weird-stripes.png" alt="Black screen with repeating columns of white"&gt;
&lt;/div&gt;

&lt;p&gt;Uhh.&lt;/p&gt;
&lt;p&gt;Well, that&amp;#8217;s weird.  Not only is the result black and white when I &lt;em&gt;definitely&lt;/em&gt; used all four shades, but the whites aren&amp;#8217;t even next to each other.  (I also had a strange effect where the screen reverted to all white after a few seconds, but can&amp;#8217;t reproduce it now; it was fixed by the same steps, though, so it may have been a quirk of a particular mGBA&amp;nbsp;build.)&lt;/p&gt;
&lt;p&gt;I&amp;#8217;ll save you my head-scratching.  I made &lt;em&gt;two&lt;/em&gt; mistakes here.  Arguably,&amp;nbsp;three!&lt;/p&gt;
&lt;p&gt;First: believe it or not, I have to specify the &lt;em&gt;palette&lt;/em&gt;.  Even in original uncolored Game Boy mode!  I can see how that&amp;#8217;s nice for doing simple fade effects or flashing colors, but I didn&amp;#8217;t suspect it would be necessary.  The monochrome palette lives at $ff47 (one of those special high addresses), so I do this before anything&amp;nbsp;else:&lt;/p&gt;
&lt;table class="highlighttable"&gt;&lt;tr&gt;&lt;td class="linenos"&gt;&lt;div class="linenodiv"&gt;&lt;pre&gt;&lt;span class="normal"&gt;1&lt;/span&gt;
&lt;span class="normal"&gt;2&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/td&gt;&lt;td class="code"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;    &lt;span class="ow"&gt;ld&lt;/span&gt; &lt;span class="nb"&gt;a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mb"&gt;%11100100&lt;/span&gt;         &lt;span class="c1"&gt;; 3 2 1 0&lt;/span&gt;
    &lt;span class="ow"&gt;ld&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mh"&gt;$ff47&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="nb"&gt;a&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;p&gt;I should really give names to some of these special addresses, but for now I&amp;#8217;m more interested in something that works than something that&amp;#8217;s nice to&amp;nbsp;read.&lt;/p&gt;
&lt;p&gt;Second: I specified the colors wrong.  I assumed that eight pixels would fit into two bytes as &lt;code&gt;AaBbCcDd EeFfGgHh&lt;/code&gt;, perhaps with some rearrangement, but a closer look at Nintendo&amp;#8217;s manual reveals that they need to be &lt;code&gt;ABCDEFGH abcdefgh&lt;/code&gt;, with the two bits for each pixel split across each byte!&amp;nbsp;Wild.&lt;/p&gt;
&lt;p&gt;Handily, rgbds has syntax for writing out pixel values directly: a backtick followed by eight of 0, 1, 2, and 3.  I just have to change my code a bit to write two bytes, eight times each.  By putting a 16-bit value in a register pair like &lt;code&gt;bc&lt;/code&gt;, I can read its high and low bytes out individually via the &lt;code&gt;b&lt;/code&gt; and &lt;code&gt;c&lt;/code&gt; registers.&lt;/p&gt;
&lt;table class="highlighttable"&gt;&lt;tr&gt;&lt;td class="linenos"&gt;&lt;div class="linenodiv"&gt;&lt;pre&gt;&lt;span class="normal"&gt;1&lt;/span&gt;
&lt;span class="normal"&gt;2&lt;/span&gt;
&lt;span class="normal"&gt;3&lt;/span&gt;
&lt;span class="normal"&gt;4&lt;/span&gt;
&lt;span class="normal"&gt;5&lt;/span&gt;
&lt;span class="normal"&gt;6&lt;/span&gt;
&lt;span class="normal"&gt;7&lt;/span&gt;
&lt;span class="normal"&gt;8&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/td&gt;&lt;td class="code"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;    &lt;span class="ow"&gt;ld&lt;/span&gt; &lt;span class="nb"&gt;hl&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mh"&gt;$8000&lt;/span&gt;
    &lt;span class="ow"&gt;ld&lt;/span&gt; &lt;span class="nb"&gt;bc&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mb"&gt;`00112233&lt;/span&gt;
    &lt;span class="k"&gt;REPT 8&lt;/span&gt;
    &lt;span class="ow"&gt;ld&lt;/span&gt; &lt;span class="nb"&gt;a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;b&lt;/span&gt;
    &lt;span class="ow"&gt;ld&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;hl&lt;/span&gt;&lt;span class="p"&gt;+],&lt;/span&gt; &lt;span class="nb"&gt;a&lt;/span&gt;
    &lt;span class="ow"&gt;ld&lt;/span&gt; &lt;span class="nb"&gt;a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;c&lt;/span&gt;
    &lt;span class="ow"&gt;ld&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;hl&lt;/span&gt;&lt;span class="p"&gt;+],&lt;/span&gt; &lt;span class="nb"&gt;a&lt;/span&gt;
    &lt;span class="k"&gt;ENDR&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;p&gt;Third: strictly speaking, I don&amp;#8217;t &lt;em&gt;think&lt;/em&gt; I should be writing to $8000 while the screen is on, because the screen may be trying to read from it at the same time.  It does happen to work in this emulator, but I have no idea whether it would work on actual hardware.  I&amp;#8217;m not going to worry too much about this test code; most likely, tile loading will happen all in one place in the real game, and I can figure out any issues&amp;nbsp;then.&lt;/p&gt;
&lt;p&gt;This is one of those places where the manual is oddly vague.  It dedicates two whole pages to diagrams of how sprites are drawn when they overlap, yet &lt;em&gt;when I can write to display &lt;span class="caps"&gt;RAM&lt;/span&gt;&lt;/em&gt; is left&amp;nbsp;implicit.&lt;/p&gt;
&lt;p&gt;Well, whatever.  It works on &lt;em&gt;my&lt;/em&gt;&amp;nbsp;machine.&lt;/p&gt;
&lt;div class="prose-full-illustration"&gt;
&lt;img src="https://eev.ee/media/cheezball/00c-mono-gradient.png" alt="Stripes of varying shades of gray"&gt;
&lt;/div&gt;

&lt;p&gt;Success!  I made a thing for the Game&amp;nbsp;Boy.&lt;/p&gt;
&lt;p&gt;Ah, but what I &lt;em&gt;wanted&lt;/em&gt; was a thing for the Game Boy &lt;em&gt;Color&lt;/em&gt;.  That shouldn&amp;#8217;t be too much&amp;nbsp;harder.&lt;/p&gt;
&lt;h2 id="now-in-technicolor"&gt;&lt;a class="toclink" href="#now-in-technicolor"&gt;Now in&amp;nbsp;Technicolor&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;First I update my Makefile to pass the &lt;code&gt;-C&lt;/code&gt; flag to &lt;code&gt;rgbfix&lt;/code&gt;.  That tells it to set a flag in the &lt;span class="caps"&gt;ROM&lt;/span&gt; header to indicate that this game is &lt;em&gt;only&lt;/em&gt; intended for the Game Boy Color, and &lt;em&gt;won&amp;#8217;t work&lt;/em&gt; on the original Game Boy.  (In order to pass Nintendo certification, I&amp;#8217;ll need an &lt;a href="http://www.vgmuseum.com/features/warn/"&gt;error screen&lt;/a&gt; when the game &lt;em&gt;is&lt;/em&gt; run on a non-Color Game Boy, but that can come later.  Also, I don&amp;#8217;t actually know how to do&amp;nbsp;that.)&lt;/p&gt;
&lt;p&gt;Oh, and I&amp;#8217;ll change the file extension from &lt;code&gt;.gb&lt;/code&gt; to &lt;code&gt;.gbc&lt;/code&gt;.  And while I&amp;#8217;m in here, I might as well repeat myself &lt;em&gt;slightly&lt;/em&gt; less in this bad, bad&amp;nbsp;Makefile.&lt;/p&gt;
&lt;table class="highlighttable"&gt;&lt;tr&gt;&lt;td class="linenos"&gt;&lt;div class="linenodiv"&gt;&lt;pre&gt;&lt;span class="normal"&gt;1&lt;/span&gt;
&lt;span class="normal"&gt;2&lt;/span&gt;
&lt;span class="normal"&gt;3&lt;/span&gt;
&lt;span class="normal"&gt;4&lt;/span&gt;
&lt;span class="normal"&gt;5&lt;/span&gt;
&lt;span class="normal"&gt;6&lt;/span&gt;
&lt;span class="normal"&gt;7&lt;/span&gt;
&lt;span class="normal"&gt;8&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/td&gt;&lt;td class="code"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="nv"&gt;TARGET&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; gamegirl.gbc

&lt;span class="nf"&gt;all&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="k"&gt;$(&lt;/span&gt;&lt;span class="nv"&gt;TARGET&lt;/span&gt;&lt;span class="k"&gt;)&lt;/span&gt;

&lt;span class="nf"&gt;$(TARGET)&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;
        rgbasm -o main.o main.rgbasm
        rgblink -o &lt;span class="k"&gt;$(&lt;/span&gt;TARGET&lt;span class="k"&gt;)&lt;/span&gt; main.o
        rgbfix -C -v -p &lt;span class="m"&gt;0&lt;/span&gt; &lt;span class="k"&gt;$(&lt;/span&gt;TARGET&lt;span class="k"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;p&gt;I &lt;em&gt;think&lt;/em&gt; &lt;code&gt;:=&lt;/code&gt; is the one I want, right?  Christ, who can remember how this syntax&amp;nbsp;works.&lt;/p&gt;
&lt;p&gt;Next I need to define a palette.  Again, everything defaults to palette zero, so I&amp;#8217;ll update that and not have to worry about specifying a palette for every&amp;nbsp;tile.&lt;/p&gt;
&lt;p&gt;This part is a bit weird.  Unlike tiles, there&amp;#8217;s not a block of addresses somewhere that contains all the palettes.  Instead, I have to write the palette to a single address one byte at a time, and the &lt;span class="caps"&gt;CPU&lt;/span&gt; will put it…  um…&amp;nbsp;somewhere.&lt;/p&gt;
&lt;p&gt;(I &lt;em&gt;think&lt;/em&gt; this is because the entire address space was already carved up for the original Game Boy, and they just didn&amp;#8217;t have room to expose palettes, but they still had a few spare high addresses they could use for new&amp;nbsp;registers.)&lt;/p&gt;
&lt;p&gt;Two registers are involved here.  The first, $ff68, specifies &lt;em&gt;which&lt;/em&gt; palette I&amp;#8217;m writing to.  It has a bunch of parts, but since I&amp;#8217;m writing to the first color of palette zero, I can leave it all zeroes.  The one exception is the high bit, which I&amp;#8217;ll explain in just a&amp;nbsp;moment.&lt;/p&gt;
&lt;table class="highlighttable"&gt;&lt;tr&gt;&lt;td class="linenos"&gt;&lt;div class="linenodiv"&gt;&lt;pre&gt;&lt;span class="normal"&gt;1&lt;/span&gt;
&lt;span class="normal"&gt;2&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/td&gt;&lt;td class="code"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;    &lt;span class="ow"&gt;ld&lt;/span&gt; &lt;span class="nb"&gt;a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mb"&gt;%10000000&lt;/span&gt;
    &lt;span class="ow"&gt;ld&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mh"&gt;$ff68&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="nb"&gt;a&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;p&gt;The other, $ff69, does the actual writing.  Each color in a palette is two bytes, and a palette contains four colors, so I need to write eight bytes to this same address.  The high bit in $ff68 is helpful here: it means that every time I write to $ff69, it should increment its internal position by one.  This is kind of like the &lt;code&gt;[hl+]&lt;/code&gt; I used above: after every write, the address increases, so I can just write all the data in&amp;nbsp;sequence.&lt;/p&gt;
&lt;p&gt;But first I need some colors!  Game Boy Color colors are &lt;span class="caps"&gt;RGB555&lt;/span&gt;, which means each color is five bits (0–31) and a full color fits in two bytes: &lt;code&gt;0bbbbbgg gggrrrrr&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;(I got this backwards initially and thought the left bits were red and the right bits were&amp;nbsp;blue.)&lt;/p&gt;
&lt;p&gt;Thus, I present, palette loading by hand.  Like before, I put the 16-bit color in &lt;code&gt;bc&lt;/code&gt; and then write out the contents of &lt;code&gt;b&lt;/code&gt; and &lt;code&gt;c&lt;/code&gt;.  (Before, the backtick syntax put the bytes in the right order; colors are little-endian, hence why I write &lt;code&gt;c&lt;/code&gt; before &lt;code&gt;b&lt;/code&gt;.)&lt;/p&gt;
&lt;table class="highlighttable"&gt;&lt;tr&gt;&lt;td class="linenos"&gt;&lt;div class="linenodiv"&gt;&lt;pre&gt;&lt;span class="normal"&gt; 1&lt;/span&gt;
&lt;span class="normal"&gt; 2&lt;/span&gt;
&lt;span class="normal"&gt; 3&lt;/span&gt;
&lt;span class="normal"&gt; 4&lt;/span&gt;
&lt;span class="normal"&gt; 5&lt;/span&gt;
&lt;span class="normal"&gt; 6&lt;/span&gt;
&lt;span class="normal"&gt; 7&lt;/span&gt;
&lt;span class="normal"&gt; 8&lt;/span&gt;
&lt;span class="normal"&gt; 9&lt;/span&gt;
&lt;span class="normal"&gt;10&lt;/span&gt;
&lt;span class="normal"&gt;11&lt;/span&gt;
&lt;span class="normal"&gt;12&lt;/span&gt;
&lt;span class="normal"&gt;13&lt;/span&gt;
&lt;span class="normal"&gt;14&lt;/span&gt;
&lt;span class="normal"&gt;15&lt;/span&gt;
&lt;span class="normal"&gt;16&lt;/span&gt;
&lt;span class="normal"&gt;17&lt;/span&gt;
&lt;span class="normal"&gt;18&lt;/span&gt;
&lt;span class="normal"&gt;19&lt;/span&gt;
&lt;span class="normal"&gt;20&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/td&gt;&lt;td class="code"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;    &lt;span class="ow"&gt;ld&lt;/span&gt; &lt;span class="nb"&gt;bc&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mb"&gt;%0111110000000000&lt;/span&gt;  &lt;span class="c1"&gt;; blue&lt;/span&gt;
    &lt;span class="ow"&gt;ld&lt;/span&gt; &lt;span class="nb"&gt;a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;c&lt;/span&gt;
    &lt;span class="ow"&gt;ld&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mh"&gt;$ff69&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="nb"&gt;a&lt;/span&gt;
    &lt;span class="ow"&gt;ld&lt;/span&gt; &lt;span class="nb"&gt;a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;b&lt;/span&gt;
    &lt;span class="ow"&gt;ld&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mh"&gt;$ff69&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="nb"&gt;a&lt;/span&gt;
    &lt;span class="ow"&gt;ld&lt;/span&gt; &lt;span class="nb"&gt;bc&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mb"&gt;%0000001111100000&lt;/span&gt;  &lt;span class="c1"&gt;; green&lt;/span&gt;
    &lt;span class="ow"&gt;ld&lt;/span&gt; &lt;span class="nb"&gt;a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;c&lt;/span&gt;
    &lt;span class="ow"&gt;ld&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mh"&gt;$ff69&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="nb"&gt;a&lt;/span&gt;
    &lt;span class="ow"&gt;ld&lt;/span&gt; &lt;span class="nb"&gt;a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;b&lt;/span&gt;
    &lt;span class="ow"&gt;ld&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mh"&gt;$ff69&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="nb"&gt;a&lt;/span&gt;
    &lt;span class="ow"&gt;ld&lt;/span&gt; &lt;span class="nb"&gt;bc&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mb"&gt;%0000000000011111&lt;/span&gt;  &lt;span class="c1"&gt;; red&lt;/span&gt;
    &lt;span class="ow"&gt;ld&lt;/span&gt; &lt;span class="nb"&gt;a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;c&lt;/span&gt;
    &lt;span class="ow"&gt;ld&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mh"&gt;$ff69&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="nb"&gt;a&lt;/span&gt;
    &lt;span class="ow"&gt;ld&lt;/span&gt; &lt;span class="nb"&gt;a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;b&lt;/span&gt;
    &lt;span class="ow"&gt;ld&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mh"&gt;$ff69&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="nb"&gt;a&lt;/span&gt;
    &lt;span class="ow"&gt;ld&lt;/span&gt; &lt;span class="nb"&gt;bc&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mb"&gt;%0111111111111111&lt;/span&gt;  &lt;span class="c1"&gt;; white&lt;/span&gt;
    &lt;span class="ow"&gt;ld&lt;/span&gt; &lt;span class="nb"&gt;a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;c&lt;/span&gt;
    &lt;span class="ow"&gt;ld&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mh"&gt;$ff69&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="nb"&gt;a&lt;/span&gt;
    &lt;span class="ow"&gt;ld&lt;/span&gt; &lt;span class="nb"&gt;a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;b&lt;/span&gt;
    &lt;span class="ow"&gt;ld&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mh"&gt;$ff69&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="nb"&gt;a&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;p&gt;Rebuild,&amp;nbsp;and:&lt;/p&gt;
&lt;div class="prose-full-illustration"&gt;
&lt;img src="https://eev.ee/media/cheezball/00d-color-gradient.png" alt="Same as before, but now the stripes are colored"&gt;
&lt;/div&gt;

&lt;p&gt;What a glorious&amp;nbsp;eyesore!&lt;/p&gt;
&lt;h2 id="to-be-continued"&gt;&lt;a class="toclink" href="#to-be-continued"&gt;To be&amp;nbsp;continued&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;That brings us up to &lt;a href="https://github.com/eevee/anise-cheezball-rising/commit/21234431b39fa466d839861d3e6513de841673eb"&gt;commit &lt;code&gt;212344&lt;/code&gt;&lt;/a&gt; and works as a good stopping&amp;nbsp;point.&lt;/p&gt;
&lt;p&gt;Next time: &lt;a href="https://eev.ee/blog/2018/06/21/cheezball-rising-drawing-a-sprite/"&gt;drawing a sprite&lt;/a&gt;!&lt;/p&gt;</content><category term="blog"></category><category term="tech"></category><category term="gamedev"></category><category term="cheezball rising"></category></entry><entry><title>A geometric Rust adventure</title><link href="https://eev.ee/blog/2018/03/30/a-geometric-rust-adventure/" rel="alternate"></link><published>2018-03-30T17:17:00-07:00</published><updated>2018-03-30T17:17:00-07:00</updated><author><name>Eevee</name></author><id>tag:eev.ee,2018-03-30:/blog/2018/03/30/a-geometric-rust-adventure/</id><summary type="html">&lt;p&gt;Hi.  Yes.  Sorry.  I’ve been trying to write this post for ages, but I’ve also been working on a huge writing project, and apparently I have a very limited amount of writing mana at my disposal.  I think this is supposed to be a Patreon reward from &lt;em&gt;January&lt;/em&gt;.  My bad.  I hope it’s super great to make up for the wait!&lt;/p&gt;
&lt;p&gt;I recently ported some math code from C++ to Rust in an attempt to do a cool thing with Doom.  Here is my story.&lt;/p&gt;
</summary><content type="html">&lt;p&gt;Hi.  Yes.  Sorry.  I&amp;#8217;ve been trying to write this post for ages, but I&amp;#8217;ve also been working on a huge writing project, and apparently I have a very limited amount of writing mana at my disposal.  I think this is supposed to be a Patreon reward from &lt;em&gt;January&lt;/em&gt;.  My bad.  I hope it&amp;#8217;s super great to make up for the&amp;nbsp;wait!&lt;/p&gt;
&lt;p&gt;I recently ported some math code from C++ to Rust in an attempt to do a cool thing with Doom.  Here is my&amp;nbsp;story.&lt;/p&gt;


&lt;h2 id="the-problem"&gt;&lt;a class="toclink" href="#the-problem"&gt;The&amp;nbsp;problem&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;I presented it recently as a &lt;a href="https://eev.ee/blog/2018/03/20/conundrum/"&gt;conundrum&lt;/a&gt; (spoilers: I solved it!), but most of those details are&amp;nbsp;unimportant.&lt;/p&gt;
&lt;p&gt;The short version is: I have some shapes.  I want to find their&amp;nbsp;intersection.&lt;/p&gt;
&lt;p&gt;Really, I want more than that: I want to drop them all on a canvas, intersect &lt;em&gt;everything&lt;/em&gt; with &lt;em&gt;everything&lt;/em&gt;, and pluck out all the resulting polygons.  The input is a set of cookie cutters, and I want to press them all down on the same sheet of dough and figure out what all the resulting contiguous pieces are.  &lt;em&gt;And&lt;/em&gt; I want to know which cookie cutter(s) each piece came&amp;nbsp;from.&lt;/p&gt;
&lt;p&gt;But intersection is a good&amp;nbsp;start.&lt;/p&gt;
&lt;div class="prose-full-illustration"&gt;
&lt;img src="https://eev.ee/media/2018-03-30-rust-adventure/goal.png" alt="Example of the goal.  Given two squares that overlap at their corners, I want to find the small overlap piece, plus the two L-shaped pieces left over from each square"&gt;
&lt;/div&gt;

&lt;p&gt;I&amp;#8217;m carefully referring to the input as &lt;em&gt;shapes&lt;/em&gt; rather than polygons, because each one could be a completely arbitrary collection of lines.  Obviously there&amp;#8217;s not much you can do with shapes that aren&amp;#8217;t even closed, but at the &lt;em&gt;very least&lt;/em&gt;, I need to handle concavity and multiple disconnected polygons that together are considered a single&amp;nbsp;input.&lt;/p&gt;
&lt;p&gt;This is a non-trivial problem with a lot of edge cases, and offhand I don&amp;#8217;t know how to solve it robustly.  I&amp;#8217;m not too eager to go figure it out from scratch, so I went hunting for something I could build&amp;nbsp;from.&lt;/p&gt;
&lt;p&gt;(Infuriatingly enough, I can just dump all the shapes out in an &lt;span class="caps"&gt;SVG&lt;/span&gt; file and any &lt;span class="caps"&gt;SVG&lt;/span&gt; viewer can immediately solve the problem, but that doesn&amp;#8217;t quite help me.  Though I have had a few people suggest I just rasterize the whole damn problem, and after all this, I&amp;#8217;m starting to think they may have a&amp;nbsp;point.)&lt;/p&gt;
&lt;p&gt;Alas, I couldn&amp;#8217;t find a Rust library for doing this.  I had a hard time finding &lt;em&gt;any&lt;/em&gt; library for doing this that wasn&amp;#8217;t a &lt;a href="https://trac.osgeo.org/geos"&gt;massive fully-featured geometry engine&lt;/a&gt;.  (I could&amp;#8217;ve used that, but I wanted to avoid non-Rust dependencies if possible, since distributing software is already enough of a&amp;nbsp;nightmare.)&lt;/p&gt;
&lt;p&gt;A Twitter follower directed me towards a paper that described how to do &lt;em&gt;very nearly&lt;/em&gt; what I wanted and nothing else: &amp;#8220;&lt;a href="https://dl.acm.org/citation.cfm?id=2494701"&gt;A simple algorithm for Boolean operations on polygons&lt;/a&gt;&amp;#8221; by F. Martínez (2013).  Being an academic paper, it&amp;#8217;s trapped in paywall hell; sorry about that.  (And as I understand it, none of the money you&amp;#8217;d pay to get the paper would even go to the authors?  Is that right?  What a horrible and predatory system for discovering and disseminating&amp;nbsp;knowledge.)&lt;/p&gt;
&lt;p&gt;The paper isn&amp;#8217;t especially long, but it does describe an awful lot of subtle details and is mostly written in terms of its own reference implementation.  Rather than write my own implementation based solely on the paper, I decided to try porting the reference implementation from C++ to&amp;nbsp;Rust.&lt;/p&gt;
&lt;p&gt;And so I fell down the rabbit&amp;nbsp;hole.&lt;/p&gt;
&lt;h2 id="the-basic-algorithm"&gt;&lt;a class="toclink" href="#the-basic-algorithm"&gt;The basic&amp;nbsp;algorithm&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Thankfully, the author has published the sample code &lt;a href="http://www4.ujaen.es/~fmartin/bool_op.html"&gt;on his own website&lt;/a&gt;, if you want to follow along.  (It&amp;#8217;s the &lt;em&gt;bottom&lt;/em&gt; link; the same author has, confusingly, published two papers on the same topic with similar titles, four years&amp;nbsp;apart.)&lt;/p&gt;
&lt;p&gt;If not, let me describe the algorithm and how the code is generally laid out.  The algorithm itself is based on a &lt;a href="https://en.wikipedia.org/wiki/Sweep_line_algorithm"&gt;sweep line&lt;/a&gt;, where a vertical line passes across the plane and ✨ &lt;em&gt;does stuff&lt;/em&gt; ✨ as it encounters various objects.  This implementation has no physical line; instead, it keeps track of which segments from the original polygon &lt;em&gt;would be&lt;/em&gt; intersecting the sweep line, which is all we really care&amp;nbsp;about.&lt;/p&gt;
&lt;div class="prose-full-illustration"&gt;
&lt;img src="https://eev.ee/media/2018-03-30-rust-adventure/sweep-line.png" alt="A vertical line is passing rightwards over a couple intersecting shapes.  The line current intersects two of the shapes' sides, and these two sides are the &amp;quot;sweep list&amp;quot;"&gt;
&lt;/div&gt;

&lt;p&gt;The code is all bundled inside a class with only a single public method, &lt;code&gt;run&lt;/code&gt;, because… that&amp;#8217;s… more object-oriented, I guess.  There are several helper methods, and state is stored in some attributes.  A rough outline of &lt;code&gt;run&lt;/code&gt; is:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;Run through all the line segments in both input polygons.  For each one, generate two &lt;code&gt;SweepEvent&lt;/code&gt;s (one for each endpoint) and add them to a &lt;code&gt;std::deque&lt;/code&gt; for&amp;nbsp;storage.&lt;/p&gt;
&lt;p&gt;Add &lt;em&gt;pointers&lt;/em&gt; to the two &lt;code&gt;SweepEvent&lt;/code&gt;s to a &lt;code&gt;std::priority_queue&lt;/code&gt;, the &lt;em&gt;event queue&lt;/em&gt;.  This queue uses a custom comparator to order the events from left to right, so the top element is always the leftmost&amp;nbsp;endpoint.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Loop over the event queue (where an &amp;#8220;event&amp;#8221; means the sweep line passed over the left or right end of a segment).  Encountering a &lt;em&gt;left&lt;/em&gt; endpoint means the sweep line is newly touching that segment, so add it to a &lt;code&gt;std::set&lt;/code&gt; called the &lt;em&gt;sweep list&lt;/em&gt;.  An important point is that &lt;code&gt;std::set&lt;/code&gt; is &lt;em&gt;ordered&lt;/em&gt;, and the sweep list uses a comparator that keeps segments in order &lt;em&gt;vertically&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;Encountering a &lt;em&gt;right&lt;/em&gt; endpoint means the sweep line is leaving a segment, so that segment is removed from the sweep&amp;nbsp;list.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;When a segment is added to the sweep list, it may have up to two neighbors: the segment above it and the segment below it.  Call &lt;code&gt;possibleIntersection&lt;/code&gt; to check whether it intersects either of those neighbors.  (This is nearly sufficient to find all intersections, which is&amp;nbsp;neat.)&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;If &lt;code&gt;possibleIntersection&lt;/code&gt; detects an intersection, it will split each segment into two pieces then and there.  The old segment is shortened in-place to become the left part, and a new segment is created for the right part.  The new endpoints at the point of intersection are added to the event&amp;nbsp;queue.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Some bookkeeping is done along the way to track which original polygons each segment is inside, and eventually the segments are reconstructed into new&amp;nbsp;polygons.&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Hopefully that&amp;#8217;s enough to follow along.  It took me an inordinately long time to tease this out.  The comments aren&amp;#8217;t especially&amp;nbsp;helpful.&lt;/p&gt;
&lt;table class="highlighttable"&gt;&lt;tr&gt;&lt;td class="linenos"&gt;&lt;div class="linenodiv"&gt;&lt;pre&gt;&lt;span class="normal"&gt;1&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/td&gt;&lt;td class="code"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;std&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;deque&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;SweepEvent&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;eventHolder&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="c1"&gt;// It holds the events generated during the computation of the boolean operation&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;h2 id="syntax-and-basic-semantics"&gt;&lt;a class="toclink" href="#syntax-and-basic-semantics"&gt;Syntax and basic&amp;nbsp;semantics&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;The first step was to get something that &lt;code&gt;rustc&lt;/code&gt; could at least &lt;em&gt;parse&lt;/em&gt;, which meant translating C++ syntax to Rust&amp;nbsp;syntax.&lt;/p&gt;
&lt;p&gt;This was surprisingly straightforward!  C++ classes become Rust structs.  (There was no inheritance here, thankfully.)  All the method declarations go away.  Method implementations only need to be indented and wrapped in &lt;code&gt;impl&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;I did encounter some unnecessarily obtuse uses of the ternary&amp;nbsp;operator:&lt;/p&gt;
&lt;table class="highlighttable"&gt;&lt;tr&gt;&lt;td class="linenos"&gt;&lt;div class="linenodiv"&gt;&lt;pre&gt;&lt;span class="normal"&gt;1&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/td&gt;&lt;td class="code"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;prevprev&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;!=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;sl&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;begin&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;?&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;--&lt;/span&gt;&lt;span class="n"&gt;prevprev&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;prevprev&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;sl&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;end&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;p&gt;Rust doesn&amp;#8217;t have a ternary — you can use a regular &lt;code&gt;if&lt;/code&gt; block as an expression — so I expanded these&amp;nbsp;out.&lt;/p&gt;
&lt;p&gt;C++ &lt;code&gt;switch&lt;/code&gt; blocks become Rust &lt;code&gt;match&lt;/code&gt; blocks, but otherwise function basically the same.  Rust&amp;#8217;s enums are scoped (&lt;em&gt;hallelujah&lt;/em&gt;), so I had to explicitly spell out where enum values came&amp;nbsp;from.&lt;/p&gt;
&lt;p&gt;The only really annoying part was changing function signatures; C++ types don&amp;#8217;t look much at all like Rust types, save for the use of angle brackets.  Rust also doesn&amp;#8217;t pass by implicit reference, so I needed to sprinkle a few &lt;code&gt;&amp;amp;&lt;/code&gt;s&amp;nbsp;around.&lt;/p&gt;
&lt;p&gt;I would&amp;#8217;ve had a much harder time here if this code had relied on any remotely esoteric C++ functionality, but thankfully it stuck to pretty vanilla&amp;nbsp;features.&lt;/p&gt;
&lt;h2 id="language-conventions"&gt;&lt;a class="toclink" href="#language-conventions"&gt;Language&amp;nbsp;conventions&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;This is a geometry problem, so the sample code unsurprisingly has its own home-grown point type.  Rather than port that type to Rust, I opted to use the popular &lt;a href="https://crates.io/crates/euclid"&gt;&lt;code&gt;euclid&lt;/code&gt;&lt;/a&gt; crate.  Not only is it code I didn&amp;#8217;t have to write, but it already does several things that the C++ code was doing by hand inline, like dot products and cross products.  And all I had to do was add one line to &lt;code&gt;Cargo.toml&lt;/code&gt; to use it!  I have no idea how anyone writes C or C++ without a package&amp;nbsp;manager.&lt;/p&gt;
&lt;p&gt;The C++ code used getters, i.e. &lt;code&gt;point.x ()&lt;/code&gt;.  I&amp;#8217;m not a huge fan of getters, though I do still appreciate the need for them in lowish-level systems languages where you want to future-proof your &lt;span class="caps"&gt;API&lt;/span&gt; and the language wants to keep a clear distinction between attribute access and method calls.  But this is a &lt;em&gt;point&lt;/em&gt;, which is nothing more than two of the same numeric type glued together; what possible future logic might you add to an accessor?  The &lt;code&gt;euclid&lt;/code&gt; authors appear to side with me and leave the coordinates as public fields, so I took great joy in removing all the superfluous&amp;nbsp;parentheses.&lt;/p&gt;
&lt;p&gt;Polygons are represented with a &lt;code&gt;Polygon&lt;/code&gt; class, which has some number of &lt;code&gt;Contour&lt;/code&gt;s.  A contour is a single contiguous loop.  Something you&amp;#8217;d usually think of as a polygon would only have one, but a shape with a hole would have two: one for the outside, one for the inside.  The weird part of this arrangement was that &lt;code&gt;Polygon&lt;/code&gt; implemented nearly the entire &lt;span class="caps"&gt;STL&lt;/span&gt; container interface, then waffled between using it and not using it throughout the rest of the code.  Rust lets anything in the same module access non-public fields, so I just skipped all that and used &lt;code&gt;polygon.contours&lt;/code&gt; directly.  Hell, I think I made &lt;code&gt;contours&lt;/code&gt; public.&lt;/p&gt;
&lt;p&gt;Finally, the &lt;code&gt;SweepEvent&lt;/code&gt; type has a &lt;code&gt;pol&lt;/code&gt; field that&amp;#8217;s declared as an &lt;code&gt;enum PolygonType&lt;/code&gt; (either &lt;code&gt;SUBJECT&lt;/code&gt; or &lt;code&gt;CLIPPING&lt;/code&gt;, to indicate which of the two inputs it is), but then some other code uses the same field as a &lt;em&gt;numeric index&lt;/em&gt; into a polygon&amp;#8217;s contours.  Boy I sure do love static typing where everything&amp;#8217;s a goddamn integer.  I wanted to extend the algorithm to work on arbitrarily many input polygons anyway, so I scrapped the enum and this became a &lt;code&gt;usize&lt;/code&gt;.&lt;/p&gt;
&lt;hr /&gt;
&lt;p&gt;Then I got to all the uses of &lt;span class="caps"&gt;STL&lt;/span&gt;.  I have only a passing familiarity with the C++ standard library, and this code actually made modest use of it, which caused some fun days-long&amp;nbsp;misunderstandings.&lt;/p&gt;
&lt;p&gt;As mentioned, the &lt;code&gt;SweepEvent&lt;/code&gt;s are stored in a &lt;code&gt;std::deque&lt;/code&gt;, which is never read from.  It took me a little thinking to realize that the deque was being used as an arena: it&amp;#8217;s the canonical home for the structs so pointers to them can be tossed around freely.  (It can&amp;#8217;t be a &lt;code&gt;std::vector&lt;/code&gt;, because that could reallocate and invalidate all the pointers; &lt;code&gt;std::deque&lt;/code&gt; is probably a doubly-linked list, and guarantees no&amp;nbsp;reallocation.)&lt;/p&gt;
&lt;p&gt;Rust&amp;#8217;s standard library does have a doubly-linked list type, but I knew I&amp;#8217;d run into ownership hell here later anyway, so I think I replaced it with a Rust &lt;code&gt;Vec&lt;/code&gt; to start with.  It won&amp;#8217;t compile either way, so whatever.  We&amp;#8217;ll get back to this in a&amp;nbsp;moment.&lt;/p&gt;
&lt;p&gt;The list of segments currently intersecting the sweep line is stored in a &lt;code&gt;std::set&lt;/code&gt;.  That type is explicitly &lt;em&gt;ordered&lt;/em&gt;, which I&amp;#8217;m very glad I knew already.  Rust has two set types, &lt;a href="https://doc.rust-lang.org/std/collections/struct.HashSet.html"&gt;&lt;code&gt;HashSet&lt;/code&gt;&lt;/a&gt; and &lt;a href="https://doc.rust-lang.org/std/collections/struct.BTreeSet.html"&gt;&lt;code&gt;BTreeSet&lt;/code&gt;&lt;/a&gt;; unsurprisingly, the former is unordered and the latter is ordered.  Dropping in &lt;code&gt;BTreeSet&lt;/code&gt; and fixing some method names got me 90% of the way&amp;nbsp;there.&lt;/p&gt;
&lt;p&gt;Which brought me to the other 90%.  See, the C++ code also relies on finding nodes &lt;em&gt;adjacent to&lt;/em&gt; the node that was just inserted, via &lt;span class="caps"&gt;STL&lt;/span&gt;&amp;nbsp;iterators.&lt;/p&gt;
&lt;table class="highlighttable"&gt;&lt;tr&gt;&lt;td class="linenos"&gt;&lt;div class="linenodiv"&gt;&lt;pre&gt;&lt;span class="normal"&gt;1&lt;/span&gt;
&lt;span class="normal"&gt;2&lt;/span&gt;
&lt;span class="normal"&gt;3&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/td&gt;&lt;td class="code"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="n"&gt;next&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;prev&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;se&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;posSL&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;it&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;sl&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;insert&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;se&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="n"&gt;first&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;prev&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;!=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;sl&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;begin&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;?&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;--&lt;/span&gt;&lt;span class="n"&gt;prev&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;prev&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;sl&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;end&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="o"&gt;++&lt;/span&gt;&lt;span class="n"&gt;next&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;p&gt;I freely admit I&amp;#8217;m bad at C++, but this seems like something that could&amp;#8217;ve used&amp;#8230;  I don&amp;#8217;t know, 1 comment.  Or variable names more than two letters long.  What it actually does&amp;nbsp;is:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;Add the current sweep event (&lt;code&gt;se&lt;/code&gt;) to the sweep list (&lt;code&gt;sl&lt;/code&gt;), which &lt;a href="http://en.cppreference.com/w/cpp/container/set/insert"&gt;returns&lt;/a&gt; a pair whose first element is an iterator pointing at the just-inserted&amp;nbsp;event.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Copies that iterator to several other variables, including &lt;code&gt;prev&lt;/code&gt; and &lt;code&gt;next&lt;/code&gt;.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;If the event was inserted at the beginning of the sweep list, set &lt;code&gt;prev&lt;/code&gt; to the sweep list&amp;#8217;s &lt;code&gt;end&lt;/code&gt; iterator, which in C++ is a legal-but-invalid iterator meaning &amp;#8220;the space after the end&amp;#8221; or something.  This is checked for in later code, to see if there &lt;em&gt;is&lt;/em&gt; a previous event to look at.  Otherwise, decrement &lt;code&gt;prev&lt;/code&gt;, so it&amp;#8217;s now pointing at the event immediately before the inserted&amp;nbsp;one.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Increment &lt;code&gt;next&lt;/code&gt; normally.  If the inserted event is last, then this will bump &lt;code&gt;next&lt;/code&gt; to the &lt;code&gt;end&lt;/code&gt; iterator&amp;nbsp;anyway.&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;In other words, I need to get the previous and next elements from a &lt;code&gt;BTreeSet&lt;/code&gt;.  Rust does have bidirectional iterators, which &lt;code&gt;BTreeSet&lt;/code&gt; supports&amp;#8230;  but &lt;code&gt;BTreeSet::insert&lt;/code&gt; only returns a &lt;code&gt;bool&lt;/code&gt; telling me whether or not anything was inserted, not the &lt;em&gt;position&lt;/em&gt;.  I came up with&amp;nbsp;this:&lt;/p&gt;
&lt;table class="highlighttable"&gt;&lt;tr&gt;&lt;td class="linenos"&gt;&lt;div class="linenodiv"&gt;&lt;pre&gt;&lt;span class="normal"&gt;1&lt;/span&gt;
&lt;span class="normal"&gt;2&lt;/span&gt;
&lt;span class="normal"&gt;3&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/td&gt;&lt;td class="code"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="kd"&gt;let&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;mut&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;maybe_below&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;active_segments&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;range&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;..&lt;/span&gt;&lt;span class="n"&gt;segment&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="n"&gt;last&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="n"&gt;map&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="kd"&gt;let&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;mut&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;maybe_above&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;active_segments&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;range&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;segment&lt;/span&gt;&lt;span class="o"&gt;..&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="n"&gt;next&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="n"&gt;map&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="n"&gt;active_segments&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;insert&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;segment&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;p&gt;The &lt;a href="https://doc.rust-lang.org/std/collections/struct.BTreeSet.html#method.range"&gt;&lt;code&gt;range&lt;/code&gt;&lt;/a&gt; method returns an iterator over a subset of the tree.  The &lt;code&gt;..&lt;/code&gt; syntax makes a range (where the right endpoint is exclusive), so &lt;code&gt;..segment&lt;/code&gt; finds the part of the tree &lt;em&gt;before&lt;/em&gt; the new segment, and &lt;code&gt;segment..&lt;/code&gt; finds the part of the tree &lt;em&gt;after&lt;/em&gt; it.  (The latter would start with the segment itself, except I haven&amp;#8217;t inserted it yet, so it&amp;#8217;s not actually&amp;nbsp;there.)&lt;/p&gt;
&lt;p&gt;Then the standard &lt;code&gt;next()&lt;/code&gt; and &lt;code&gt;last()&lt;/code&gt; methods on bidirectional iterators find me the element I actually want.  But the iterator might be empty, so they both return an &lt;code&gt;Option&lt;/code&gt;.  Also, iterators tend to return references to their contents, but in this case the contents are already references, and I don&amp;#8217;t want a double reference, so the &lt;code&gt;map&lt;/code&gt; call dereferences one layer — but only if the &lt;code&gt;Option&lt;/code&gt; contains a value.&amp;nbsp;Phew!&lt;/p&gt;
&lt;p&gt;This is slightly less efficient than the C++ code, since it has to look up where &lt;code&gt;segment&lt;/code&gt; goes &lt;em&gt;three&lt;/em&gt; times rather than just one.  I might be able to get it down to two with some more clever finagling of the iterator, but microsopic performance considerations were a low priority&amp;nbsp;here.&lt;/p&gt;
&lt;p&gt;Finally, the event queue uses a &lt;code&gt;std::priority_queue&lt;/code&gt; to keep events in a desired order and efficiently pop the next one off the&amp;nbsp;top.&lt;/p&gt;
&lt;p&gt;Except priority queues act like heaps, where the &lt;em&gt;greatest&lt;/em&gt; (i.e., &lt;em&gt;last&lt;/em&gt;) item is made&amp;nbsp;accessible.&lt;/p&gt;
&lt;h2 id="sorting-out-sorting"&gt;&lt;a class="toclink" href="#sorting-out-sorting"&gt;Sorting out&amp;nbsp;sorting&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;C++ comparison functions return &lt;code&gt;true&lt;/code&gt; to indicate that the first argument is less than the second argument.  Sweep events occur from left to right.  You generally implement sorts so that the first thing comes, erm,&amp;nbsp;first.&lt;/p&gt;
&lt;p&gt;But sweep events go in a priority queue, and priority queues surface the last item, not the first.  This C++ code handled this minor wrinkle by implementing its comparison &lt;em&gt;backwards&lt;/em&gt;.&lt;/p&gt;
&lt;table class="highlighttable"&gt;&lt;tr&gt;&lt;td class="linenos"&gt;&lt;div class="linenodiv"&gt;&lt;pre&gt;&lt;span class="normal"&gt; 1&lt;/span&gt;
&lt;span class="normal"&gt; 2&lt;/span&gt;
&lt;span class="normal"&gt; 3&lt;/span&gt;
&lt;span class="normal"&gt; 4&lt;/span&gt;
&lt;span class="normal"&gt; 5&lt;/span&gt;
&lt;span class="normal"&gt; 6&lt;/span&gt;
&lt;span class="normal"&gt; 7&lt;/span&gt;
&lt;span class="normal"&gt; 8&lt;/span&gt;
&lt;span class="normal"&gt; 9&lt;/span&gt;
&lt;span class="normal"&gt;10&lt;/span&gt;
&lt;span class="normal"&gt;11&lt;/span&gt;
&lt;span class="normal"&gt;12&lt;/span&gt;
&lt;span class="normal"&gt;13&lt;/span&gt;
&lt;span class="normal"&gt;14&lt;/span&gt;
&lt;span class="normal"&gt;15&lt;/span&gt;
&lt;span class="normal"&gt;16&lt;/span&gt;
&lt;span class="normal"&gt;17&lt;/span&gt;
&lt;span class="normal"&gt;18&lt;/span&gt;
&lt;span class="normal"&gt;19&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/td&gt;&lt;td class="code"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="k"&gt;struct&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nc"&gt;SweepEventComp&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;public&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;std&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;binary_function&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;SweepEvent&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;SweepEvent&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;bool&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;// for sorting sweep events&lt;/span&gt;
&lt;span class="c1"&gt;// Compare two sweep events&lt;/span&gt;
&lt;span class="c1"&gt;// Return true means that e1 is placed at the event queue after e2, i.e,, e1 is processed by the algorithm after e2&lt;/span&gt;
&lt;span class="kt"&gt;bool&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;operator&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;const&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;SweepEvent&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;e1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;const&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;SweepEvent&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;e2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;e1&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;point&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;e2&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;point&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;// Different x-coordinate&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;true&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;e2&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;point&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;e1&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;point&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;// Different x-coordinate&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;false&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;e1&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;point&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;y&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;!=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;e2&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;point&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;y&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;// Different points, but same x-coordinate. The event with lower y-coordinate is processed first&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;e1&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;point&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;y&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;e2&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;point&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;y&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;e1&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;left&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;!=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;e2&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;left&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;// Same point, but one is a left endpoint and the other a right endpoint. The right endpoint is processed first&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;e1&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;left&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="c1"&gt;// Same point, both events are left endpoints or both are right endpoints.&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;signedArea&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;e1&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;point&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;e1&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;otherEvent&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;point&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;e2&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;otherEvent&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;point&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;!=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;// not collinear&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;e1&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;above&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;e2&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;otherEvent&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;point&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;// the event associate to the bottom segment is processed first&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;e1&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;pol&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;e2&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;pol&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;p&gt;Maybe it&amp;#8217;s just me, but I had a hell of a time just figuring out what problem this was even trying to solve.  I &lt;em&gt;still&lt;/em&gt; have to reread it several times whenever I look at it, to make sure I&amp;#8217;m getting the right things&amp;nbsp;backwards.&lt;/p&gt;
&lt;p&gt;Making this even more ridiculous is that there&amp;#8217;s a &lt;em&gt;second&lt;/em&gt; implementation of this same sort, with the same name, in another file — and that one&amp;#8217;s implemented &lt;em&gt;forwards&lt;/em&gt;.  And doesn&amp;#8217;t use a tiebreaker.  I don&amp;#8217;t entirely understand how this even compiles, but it&amp;nbsp;does!&lt;/p&gt;
&lt;p&gt;I painstakingly translated this &lt;em&gt;forwards&lt;/em&gt; to Rust.  Unlike the &lt;span class="caps"&gt;STL&lt;/span&gt;, Rust doesn&amp;#8217;t take custom comparators for its containers, so I had to implement ordering on the types themselves (which makes sense, anyway).  I wrapped everything in the priority queue in a &lt;a href="https://doc.rust-lang.org/core/cmp/struct.Reverse.html"&gt;&lt;code&gt;Reverse&lt;/code&gt;&lt;/a&gt;, which does what it sounds&amp;nbsp;like.&lt;/p&gt;
&lt;p&gt;I&amp;#8217;m fairly pleased with Rust&amp;#8217;s ordering model.  Most of the work is done in &lt;a href="https://doc.rust-lang.org/std/cmp/trait.Ord.html"&gt;&lt;code&gt;Ord&lt;/code&gt;&lt;/a&gt;, a trait with a &lt;code&gt;cmp()&lt;/code&gt; method returning an &lt;code&gt;Ordering&lt;/code&gt; (one of &lt;code&gt;Less&lt;/code&gt;, &lt;code&gt;Equal&lt;/code&gt;, and &lt;code&gt;Greater&lt;/code&gt;).  No magic numbers, no need to implement all six ordering methods!  It&amp;#8217;s incredible.  &lt;code&gt;Ordering&lt;/code&gt; even has some handy methods on it, so the usual case of &amp;#8220;order by this, then by this&amp;#8221; can be written&amp;nbsp;as:&lt;/p&gt;
&lt;table class="highlighttable"&gt;&lt;tr&gt;&lt;td class="linenos"&gt;&lt;div class="linenodiv"&gt;&lt;pre&gt;&lt;span class="normal"&gt;1&lt;/span&gt;
&lt;span class="normal"&gt;2&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/td&gt;&lt;td class="code"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;point&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;cmp&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;other&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;point&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;then&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;point&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="n"&gt;y&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;cmp&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;other&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;point&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="n"&gt;y&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;p&gt;&lt;em&gt;Well.&lt;/em&gt;  Just kidding!  It&amp;#8217;s not &lt;em&gt;quite&lt;/em&gt; that easy.  You see, the points here are composed of floats, and floats have the fun property that not all of them are comparable.  Specifically, NaN is not less than, greater than, &lt;em&gt;or&lt;/em&gt; equal to anything else, including itself.  So &lt;span class="caps"&gt;IEEE&lt;/span&gt; 754 float ordering &lt;em&gt;cannot&lt;/em&gt; be expressed with &lt;code&gt;Ord&lt;/code&gt;.  Unless you want to just make up an answer for NaN, but Rust doesn&amp;#8217;t tend to do&amp;nbsp;that.&lt;/p&gt;
&lt;p&gt;Rust&amp;#8217;s float types thus implement the weaker &lt;a href="https://doc.rust-lang.org/std/cmp/trait.PartialOrd.html"&gt;&lt;code&gt;PartialOrd&lt;/code&gt;&lt;/a&gt;, whose method returns an &lt;code&gt;Option&amp;lt;Ordering&amp;gt;&lt;/code&gt; instead.  That makes the above example slightly&amp;nbsp;uglier:&lt;/p&gt;
&lt;table class="highlighttable"&gt;&lt;tr&gt;&lt;td class="linenos"&gt;&lt;div class="linenodiv"&gt;&lt;pre&gt;&lt;span class="normal"&gt;1&lt;/span&gt;
&lt;span class="normal"&gt;2&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/td&gt;&lt;td class="code"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;point&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;partial_cmp&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;other&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;point&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="n"&gt;unwrap&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;then&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;point&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="n"&gt;y&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;partial_cmp&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;other&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;point&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="n"&gt;y&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="n"&gt;unwrap&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;p&gt;Also, since I use &lt;code&gt;unwrap()&lt;/code&gt; here, this code will panic and take the whole program down if the points are infinite or NaN.  Don&amp;#8217;t do&amp;nbsp;that.&lt;/p&gt;
&lt;p&gt;This caused some minor inconveniences in other places; for example, the general-purpose &lt;a href="https://doc.rust-lang.org/std/cmp/fn.min.html"&gt;&lt;code&gt;cmp::min()&lt;/code&gt;&lt;/a&gt; doesn&amp;#8217;t work on floats, because it requires an &lt;code&gt;Ord&lt;/code&gt;-erable type.  Thankfully there&amp;#8217;s a &lt;a href="https://doc.rust-lang.org/std/primitive.f64.html#method.min"&gt;&lt;code&gt;f64::min()&lt;/code&gt;&lt;/a&gt;, which handles a NaN by returning the other&amp;nbsp;argument.&lt;/p&gt;
&lt;p&gt;(Cool story: for the longest time I had this code using &lt;code&gt;f32&lt;/code&gt;s.  I&amp;#8217;m used to translating &lt;code&gt;int&lt;/code&gt; to &amp;#8220;32 bits&amp;#8221;, and apparently that instinct kicked in for floats as well, even floats spelled &lt;code&gt;double&lt;/code&gt;.)&lt;/p&gt;
&lt;p&gt;The only other sorting adventure was&amp;nbsp;this:&lt;/p&gt;
&lt;table class="highlighttable"&gt;&lt;tr&gt;&lt;td class="linenos"&gt;&lt;div class="linenodiv"&gt;&lt;pre&gt;&lt;span class="normal"&gt; 1&lt;/span&gt;
&lt;span class="normal"&gt; 2&lt;/span&gt;
&lt;span class="normal"&gt; 3&lt;/span&gt;
&lt;span class="normal"&gt; 4&lt;/span&gt;
&lt;span class="normal"&gt; 5&lt;/span&gt;
&lt;span class="normal"&gt; 6&lt;/span&gt;
&lt;span class="normal"&gt; 7&lt;/span&gt;
&lt;span class="normal"&gt; 8&lt;/span&gt;
&lt;span class="normal"&gt; 9&lt;/span&gt;
&lt;span class="normal"&gt;10&lt;/span&gt;
&lt;span class="normal"&gt;11&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/td&gt;&lt;td class="code"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="c1"&gt;// Due to overlapping edges the resultEvents array can be not wholly sorted&lt;/span&gt;
&lt;span class="kt"&gt;bool&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;sorted&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;false&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="k"&gt;while&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="n"&gt;sorted&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;sorted&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;true&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;for&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;unsigned&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;resultEvents&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;size&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;++&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;resultEvents&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;size&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;sec&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;resultEvents&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;resultEvents&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;]))&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="n"&gt;std&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;swap&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;resultEvents&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;resultEvents&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;]);&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="n"&gt;sorted&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;false&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;p&gt;(I originally misread this comment as saying &amp;#8220;the array &lt;em&gt;cannot be&lt;/em&gt; wholly sorted&amp;#8221; and had no idea why that would be the case, or why the author would then immediately attempt to bubble sort&amp;nbsp;it.)&lt;/p&gt;
&lt;p&gt;I&amp;#8217;m &lt;em&gt;still&lt;/em&gt; not sure why this uses an ad-hoc sort instead of &lt;code&gt;std::sort&lt;/code&gt;.  But I&amp;#8217;m used to taking for granted that general-purpose sorting implementations are tuned to work well for almost-sorted data, like Python&amp;#8217;s.  Maybe C++ is untrustworthy here, for some reason.  I replaced it with a call to &lt;code&gt;.sort()&lt;/code&gt; and all seemed&amp;nbsp;fine.&lt;/p&gt;
&lt;p&gt;Phew!  We&amp;#8217;re getting there.  Finally, my code appears to&amp;nbsp;type-check.&lt;/p&gt;
&lt;p&gt;But now I see storm clouds gathering on the&amp;nbsp;horizon.&lt;/p&gt;
&lt;h2 id="ownership-hell"&gt;&lt;a class="toclink" href="#ownership-hell"&gt;Ownership&amp;nbsp;hell&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;I have a problem.  I somehow run into this problem every single time I use Rust.  The solutions are never especially satisfying, and all the hacks I might use if forced to write C++ turn out to be unsound, which is even &lt;em&gt;more&lt;/em&gt; annoying because &lt;code&gt;rustc&lt;/code&gt; is just sitting there with this smug &amp;#8220;I told you so expression&amp;#8221;&amp;nbsp;and—&lt;/p&gt;
&lt;p&gt;The problem is &lt;em&gt;ownership&lt;/em&gt;, which Rust is fundamentally built on.  Any given value must have &lt;em&gt;exactly one&lt;/em&gt; owner, and Rust must be able to &lt;strong&gt;statically&lt;/strong&gt; convince itself&amp;nbsp;that:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;No reference to a value outlives that&amp;nbsp;value.&lt;/li&gt;
&lt;li&gt;If a &lt;em&gt;mutable&lt;/em&gt; reference to a value exists, no other references to that value exist at the same&amp;nbsp;time.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;This is the core of Rust.  It guarantees &lt;em&gt;at compile time&lt;/em&gt; that you &lt;em&gt;cannot&lt;/em&gt; lose pointers to allocated memory, you &lt;em&gt;cannot&lt;/em&gt; double-free, you &lt;em&gt;cannot&lt;/em&gt; have dangling&amp;nbsp;pointers.&lt;/p&gt;
&lt;p&gt;It also completely thwarts a lot of approaches you might be inclined to take if you come from managed languages (where who cares, the &lt;span class="caps"&gt;GC&lt;/span&gt; will take care of it) or C++ (where you just throw pointers everywhere and hope for the best&amp;nbsp;apparently).&lt;/p&gt;
&lt;p&gt;For example, pointer loops are impossible.  Rust&amp;#8217;s understanding of ownership and lifetimes is hierarchical, and it simply cannot express loops.  (Rust&amp;#8217;s own doubly-linked list type uses raw pointers and unsafe code &lt;a href="https://doc.rust-lang.org/src/alloc/linked_list.rs.html#46-51"&gt;under the hood&lt;/a&gt;, where &amp;#8220;unsafe&amp;#8221; is an escape hatch for the usual ownership rules.  Since I only recently realized that pointers to the inside of a mutable &lt;code&gt;Vec&lt;/code&gt; are a bad idea, I figure I should probably not be writing unsafe code&amp;nbsp;myself.)&lt;/p&gt;
&lt;p&gt;This throws a few wrenches in the&amp;nbsp;works.&lt;/p&gt;
&lt;h3 id="problem-the-first-pointer-loops"&gt;&lt;a class="toclink" href="#problem-the-first-pointer-loops"&gt;Problem the first: pointer&amp;nbsp;loops&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;I immediately ran into trouble with the &lt;code&gt;SweepEvent&lt;/code&gt; struct itself.  A &lt;code&gt;SweepEvent&lt;/code&gt; pulls double duty: it represents one endpoint of a segment, but each &lt;em&gt;left&lt;/em&gt; endpoint also handles bookkeeping for the segment itself — which means that most of the fields on a right endpoint are unused.  Also, and more importantly, each &lt;code&gt;SweepEvent&lt;/code&gt; has a pointer to the corresponding &lt;code&gt;SweepEvent&lt;/code&gt; at the &lt;em&gt;other end&lt;/em&gt; of the same segment.  So a pair of &lt;code&gt;SweepEvent&lt;/code&gt;s point to each&amp;nbsp;other.&lt;/p&gt;
&lt;p&gt;Rust frowns upon this.  In retrospect, I think I could&amp;#8217;ve kept it working, but I also think I&amp;#8217;m wrong about&amp;nbsp;that.&lt;/p&gt;
&lt;p&gt;My first step was to wrench &lt;code&gt;SweepEvent&lt;/code&gt; apart.  I moved all of the segment-stuff (which is virtually all of it) into a single &lt;code&gt;SweepSegment&lt;/code&gt; type, and then populated the event queue with a &lt;code&gt;SweepEndpoint&lt;/code&gt; tuple struct, similar&amp;nbsp;to:&lt;/p&gt;
&lt;table class="highlighttable"&gt;&lt;tr&gt;&lt;td class="linenos"&gt;&lt;div class="linenodiv"&gt;&lt;pre&gt;&lt;span class="normal"&gt;1&lt;/span&gt;
&lt;span class="normal"&gt;2&lt;/span&gt;
&lt;span class="normal"&gt;3&lt;/span&gt;
&lt;span class="normal"&gt;4&lt;/span&gt;
&lt;span class="normal"&gt;5&lt;/span&gt;
&lt;span class="normal"&gt;6&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/td&gt;&lt;td class="code"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="k"&gt;enum&lt;/span&gt; &lt;span class="nc"&gt;SegmentEnd&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;Left&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;Right&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;

&lt;span class="k"&gt;struct&lt;/span&gt; &lt;span class="nc"&gt;SweepEndpoint&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&amp;#39;&lt;/span&gt;&lt;span class="na"&gt;a&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&amp;#39;&lt;/span&gt;&lt;span class="na"&gt;a&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;SweepSegment&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;SegmentEnd&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;p&gt;This makes &lt;code&gt;SweepEndpoint&lt;/code&gt; essentially a tuple with a name.  The &lt;code&gt;'a&lt;/code&gt; is a &lt;em&gt;lifetime&lt;/em&gt; and says, more or less, that a &lt;code&gt;SweepEndpoint&lt;/code&gt; cannot outlive the &lt;code&gt;SweepSegment&lt;/code&gt; it references.  Makes&amp;nbsp;sense.&lt;/p&gt;
&lt;p&gt;Problem solved!  I no longer have mutually referential pointers.  But I do still have pointers (well, references), and they have to point &lt;em&gt;to&lt;/em&gt;&amp;nbsp;something.&lt;/p&gt;
&lt;h3 id="problem-the-second-wheres-all-the-data"&gt;&lt;a class="toclink" href="#problem-the-second-wheres-all-the-data"&gt;Problem the second: where's all the&amp;nbsp;data&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Which brings me to the problem I always run into with Rust.  I have a bucket of &lt;em&gt;things&lt;/em&gt;, and I need to refer to some of them multiple&amp;nbsp;times.&lt;/p&gt;
&lt;p&gt;I tried half a dozen different approaches here and don&amp;#8217;t clearly remember all of them, but I &lt;em&gt;think&lt;/em&gt; my core problem went as follows.  I translated the C++ class to a Rust struct with some methods hanging off of it.  A simplified version might look like&amp;nbsp;this.&lt;/p&gt;
&lt;table class="highlighttable"&gt;&lt;tr&gt;&lt;td class="linenos"&gt;&lt;div class="linenodiv"&gt;&lt;pre&gt;&lt;span class="normal"&gt;1&lt;/span&gt;
&lt;span class="normal"&gt;2&lt;/span&gt;
&lt;span class="normal"&gt;3&lt;/span&gt;
&lt;span class="normal"&gt;4&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/td&gt;&lt;td class="code"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="k"&gt;struct&lt;/span&gt; &lt;span class="nc"&gt;Algorithm&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;arena&lt;/span&gt;: &lt;span class="nc"&gt;LinkedList&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;SweepSegment&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;event_queue&lt;/span&gt;: &lt;span class="nc"&gt;BinaryHeap&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;SweepEndpoint&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;p&gt;Ah, hang on — &lt;code&gt;SweepEndpoint&lt;/code&gt; needs to be annotated with a &lt;em&gt;lifetime&lt;/em&gt;, so Rust can enforce that those endpoints don&amp;#8217;t live longer than the segments they refer to.  No&amp;nbsp;problem?&lt;/p&gt;
&lt;table class="highlighttable"&gt;&lt;tr&gt;&lt;td class="linenos"&gt;&lt;div class="linenodiv"&gt;&lt;pre&gt;&lt;span class="normal"&gt;1&lt;/span&gt;
&lt;span class="normal"&gt;2&lt;/span&gt;
&lt;span class="normal"&gt;3&lt;/span&gt;
&lt;span class="normal"&gt;4&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/td&gt;&lt;td class="code"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="k"&gt;struct&lt;/span&gt; &lt;span class="nc"&gt;Algorithm&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&amp;#39;&lt;/span&gt;&lt;span class="na"&gt;a&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;arena&lt;/span&gt;: &lt;span class="nc"&gt;LinkedList&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;SweepSegment&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;event_queue&lt;/span&gt;: &lt;span class="nc"&gt;BinaryHeap&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;SweepEndpoint&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&amp;#39;&lt;/span&gt;&lt;span class="na"&gt;a&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;p&gt;Okay!  Now for some&amp;nbsp;methods.&lt;/p&gt;
&lt;table class="highlighttable"&gt;&lt;tr&gt;&lt;td class="linenos"&gt;&lt;div class="linenodiv"&gt;&lt;pre&gt;&lt;span class="normal"&gt;1&lt;/span&gt;
&lt;span class="normal"&gt;2&lt;/span&gt;
&lt;span class="normal"&gt;3&lt;/span&gt;
&lt;span class="normal"&gt;4&lt;/span&gt;
&lt;span class="normal"&gt;5&lt;/span&gt;
&lt;span class="normal"&gt;6&lt;/span&gt;
&lt;span class="normal"&gt;7&lt;/span&gt;
&lt;span class="normal"&gt;8&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/td&gt;&lt;td class="code"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="k"&gt;fn&lt;/span&gt; &lt;span class="nf"&gt;run&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="k"&gt;mut&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;arena&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;push_back&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;SweepSegment&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;: &lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;});&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;event_queue&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;push&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;SweepEndpoint&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;arena&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;back&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="n"&gt;unwrap&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;SegmentEnd&lt;/span&gt;::&lt;span class="n"&gt;Left&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;event_queue&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;push&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;SweepEndpoint&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;arena&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;back&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="n"&gt;unwrap&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;SegmentEnd&lt;/span&gt;::&lt;span class="n"&gt;Right&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;for&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;event&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;in&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;event_queue&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="fm"&gt;println!&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;{:?}&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;event&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;p&gt;Aaand&amp;#8230;  &lt;em&gt;this&lt;/em&gt; doesn&amp;#8217;t work.  Rust &amp;#8220;cannot infer an appropriate lifetime for autoref due to conflicting requirements&amp;#8221;.  The trouble is that &lt;code&gt;self.arena.back()&lt;/code&gt; takes a reference to &lt;code&gt;self.arena&lt;/code&gt;, and then I put that reference in the event queue.  But I promised that everything in the event queue has lifetime &lt;code&gt;'a&lt;/code&gt;, and I don&amp;#8217;t actually know how long &lt;em&gt;&lt;code&gt;self&lt;/code&gt;&lt;/em&gt; lives here; I only know that it can&amp;#8217;t &lt;em&gt;outlive&lt;/em&gt; &lt;code&gt;'a&lt;/code&gt;, because that would invalidate the references it&amp;nbsp;holds.&lt;/p&gt;
&lt;p&gt;A little random guessing let me to change &lt;code&gt;&amp;amp;mut self&lt;/code&gt; to &lt;code&gt;&amp;amp;'a mut self&lt;/code&gt; — which is fine because the entire &lt;code&gt;impl&lt;/code&gt; block this lives in is already parameterized by &lt;code&gt;'a&lt;/code&gt; — and that makes this compile!  Hooray!  I think that&amp;#8217;s because I&amp;#8217;m saying &lt;code&gt;self&lt;/code&gt; itself has exactly the same lifetime as the references it holds onto, which is true, since it&amp;#8217;s referring to&amp;nbsp;itself.&lt;/p&gt;
&lt;p&gt;Let&amp;#8217;s get a little more ambitious and try having &lt;em&gt;two&lt;/em&gt;&amp;nbsp;segments.&lt;/p&gt;
&lt;table class="highlighttable"&gt;&lt;tr&gt;&lt;td class="linenos"&gt;&lt;div class="linenodiv"&gt;&lt;pre&gt;&lt;span class="normal"&gt; 1&lt;/span&gt;
&lt;span class="normal"&gt; 2&lt;/span&gt;
&lt;span class="normal"&gt; 3&lt;/span&gt;
&lt;span class="normal"&gt; 4&lt;/span&gt;
&lt;span class="normal"&gt; 5&lt;/span&gt;
&lt;span class="normal"&gt; 6&lt;/span&gt;
&lt;span class="normal"&gt; 7&lt;/span&gt;
&lt;span class="normal"&gt; 8&lt;/span&gt;
&lt;span class="normal"&gt; 9&lt;/span&gt;
&lt;span class="normal"&gt;10&lt;/span&gt;
&lt;span class="normal"&gt;11&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/td&gt;&lt;td class="code"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="k"&gt;fn&lt;/span&gt; &lt;span class="nf"&gt;run&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&amp;#39;&lt;/span&gt;&lt;span class="na"&gt;a&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;mut&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;arena&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;push_back&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;SweepSegment&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;: &lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;});&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;event_queue&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;push&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;SweepEndpoint&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;arena&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;back&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="n"&gt;unwrap&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;SegmentEnd&lt;/span&gt;::&lt;span class="n"&gt;Left&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;event_queue&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;push&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;SweepEndpoint&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;arena&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;back&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="n"&gt;unwrap&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;SegmentEnd&lt;/span&gt;::&lt;span class="n"&gt;Right&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;arena&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;push_back&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;SweepSegment&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;: &lt;span class="mi"&gt;17&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;});&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;event_queue&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;push&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;SweepEndpoint&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;arena&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;back&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="n"&gt;unwrap&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;SegmentEnd&lt;/span&gt;::&lt;span class="n"&gt;Left&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;event_queue&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;push&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;SweepEndpoint&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;arena&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;back&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="n"&gt;unwrap&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;SegmentEnd&lt;/span&gt;::&lt;span class="n"&gt;Right&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;for&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;event&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;in&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;event_queue&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="fm"&gt;println!&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;{:?}&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;event&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;p&gt;Whoops!  Rust complains that I&amp;#8217;m trying to mutate &lt;code&gt;self.arena&lt;/code&gt; while other stuff is referring to it.  And, yes, that&amp;#8217;s true — I have references to it in the event queue, and Rust is preventing me from potentially deleting everything from the queue when references to it still exist.  I&amp;#8217;m not &lt;em&gt;actually&lt;/em&gt; deleting anything here, of course (though I &lt;em&gt;could&lt;/em&gt; be if this were a &lt;code&gt;Vec&lt;/code&gt;!), but Rust&amp;#8217;s type system can&amp;#8217;t encode that (and I dread the thought of a type system that&amp;nbsp;can).&lt;/p&gt;
&lt;p&gt;I struggled with this for a while, and rapidly encountered another complete&amp;nbsp;showstopper:&lt;/p&gt;
&lt;table class="highlighttable"&gt;&lt;tr&gt;&lt;td class="linenos"&gt;&lt;div class="linenodiv"&gt;&lt;pre&gt;&lt;span class="normal"&gt;1&lt;/span&gt;
&lt;span class="normal"&gt;2&lt;/span&gt;
&lt;span class="normal"&gt;3&lt;/span&gt;
&lt;span class="normal"&gt;4&lt;/span&gt;
&lt;span class="normal"&gt;5&lt;/span&gt;
&lt;span class="normal"&gt;6&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/td&gt;&lt;td class="code"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="k"&gt;fn&lt;/span&gt; &lt;span class="nf"&gt;run&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&amp;#39;&lt;/span&gt;&lt;span class="na"&gt;a&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;mut&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;mutate_something&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;mutate_something&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;

&lt;span class="k"&gt;fn&lt;/span&gt; &lt;span class="nf"&gt;mutate_something&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&amp;#39;&lt;/span&gt;&lt;span class="na"&gt;a&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;mut&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{}&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;p&gt;Rust objects that I&amp;#8217;m trying to borrow &lt;code&gt;self&lt;/code&gt; mutably, twice — once for the first call, once for the&amp;nbsp;second.&lt;/p&gt;
&lt;p&gt;But why?  A borrow is supposed to end automatically once it&amp;#8217;s no longer used, right?  Maybe if I throw some braces around it for scope&amp;#8230;  nope, that doesn&amp;#8217;t help&amp;nbsp;either.&lt;/p&gt;
&lt;p&gt;It&amp;#8217;s true that borrows usually end automatically, but here I have &lt;strong&gt;explicitly&lt;/strong&gt; told Rust that &lt;code&gt;mutate_something()&lt;/code&gt; should borrow with the lifetime &lt;code&gt;'a&lt;/code&gt;, which is the same as the lifetime in &lt;code&gt;run()&lt;/code&gt;.  So the first call explicitly borrows &lt;code&gt;self&lt;/code&gt; for &lt;em&gt;at least&lt;/em&gt; the rest of the method.  Removing the lifetime from &lt;code&gt;mutate_something()&lt;/code&gt; does fix this error, but if that method tries to add new segments, I&amp;#8217;m back to the original&amp;nbsp;problem.&lt;/p&gt;
&lt;p&gt;Oh no.  The mutation in the C++ code is several calls deep.  Porting it directly seems nearly&amp;nbsp;impossible.&lt;/p&gt;
&lt;p&gt;The typical solution here — at least, the first thing people suggest to me on Twitter — is to wrap basically everything everywhere in &lt;code&gt;Rc&amp;lt;RefCell&amp;lt;T&amp;gt;&amp;gt;&lt;/code&gt;, which gives you something that&amp;#8217;s reference-counted (avoiding questions of ownership) and defers borrow checks until runtime (avoiding questions of mutable borrows).  But that seems &lt;em&gt;pretty&lt;/em&gt; heavy-handed here — not only does &lt;code&gt;RefCell&lt;/code&gt; add &lt;code&gt;.borrow()&lt;/code&gt; noise anywhere you actually want to interact with the underlying value, but do I really need to refcount these tiny structs that only hold a handful of floats&amp;nbsp;each?&lt;/p&gt;
&lt;p&gt;I set out to find a middle&amp;nbsp;ground.&lt;/p&gt;
&lt;h3 id="solution-kind-of"&gt;&lt;a class="toclink" href="#solution-kind-of"&gt;Solution, kind&amp;nbsp;of&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;I really, really didn&amp;#8217;t want to perform serious surgery on this code just to get it to build.  I still didn&amp;#8217;t know if it worked at all, and now I had to rearrange it without being able to check if I was breaking it further.  (This isn&amp;#8217;t Rust&amp;#8217;s fault; it&amp;#8217;s a natural problem with porting between fairly different&amp;nbsp;paradigms.)&lt;/p&gt;
&lt;p&gt;So I kind of hacked it into working with minimal changes, producing a grotesque abomination which I&amp;#8217;m ashamed to link to.  Here&amp;#8217;s&amp;nbsp;how!&lt;/p&gt;
&lt;p&gt;First, I got rid of the class.  It turns out this makes lifetime juggling &lt;em&gt;much&lt;/em&gt; easier right off the bat.  I&amp;#8217;m pretty sure Rust considers everything in a struct to be destroyed simultaneously (though in practice it guarantees it&amp;#8217;ll destroy fields in order), which doesn&amp;#8217;t leave much wiggle room.  Locals within a function, on the other hand, can each have their own distinct lifetimes, which solves the problem of expressing that the borrows won&amp;#8217;t outlive the&amp;nbsp;arena.&lt;/p&gt;
&lt;p&gt;Speaking of the arena, I solved the mutability problem there by switching to&amp;#8230;  an arena!  The &lt;a href="https://crates.io/crates/typed-arena"&gt;&lt;code&gt;typed-arena&lt;/code&gt;&lt;/a&gt; crate (a port of a type used within Rust itself, I think) is an allocator — you give it a value, and it gives you back a reference, and the reference is guaranteed to be valid for as long as the arena exists.  The method that does this is sneaky and takes &lt;code&gt;&amp;amp;self&lt;/code&gt; rather than &lt;code&gt;&amp;amp;mut self&lt;/code&gt;, so Rust doesn&amp;#8217;t &lt;em&gt;know&lt;/em&gt; you&amp;#8217;re mutating the arena and won&amp;#8217;t complain.  (One drawback is that the arena will &lt;em&gt;never free&lt;/em&gt; anything you give to it, but that&amp;#8217;s not a big problem&amp;nbsp;here.)&lt;/p&gt;
&lt;hr /&gt;
&lt;p&gt;My next problem was with mutation.  The main loop repeatedly calls &lt;code&gt;possibleIntersection&lt;/code&gt; with pairs of segments, which &lt;em&gt;can&lt;/em&gt; split either or both segment.  Rust definitely doesn&amp;#8217;t like that — I&amp;#8217;d have to pass in two &lt;code&gt;&amp;amp;mut&lt;/code&gt;s, both of which are mutable references into the same arena, &lt;em&gt;and&lt;/em&gt; I&amp;#8217;d have a bunch of immutable references into that arena in the sweep list and elsewhere.  This isn&amp;#8217;t going to&amp;nbsp;fly.&lt;/p&gt;
&lt;p&gt;This is kind of a shame, and is one place where Rust seems a little overzealous.  Something like this seems like it ought to be perfectly&amp;nbsp;valid:&lt;/p&gt;
&lt;table class="highlighttable"&gt;&lt;tr&gt;&lt;td class="linenos"&gt;&lt;div class="linenodiv"&gt;&lt;pre&gt;&lt;span class="normal"&gt;1&lt;/span&gt;
&lt;span class="normal"&gt;2&lt;/span&gt;
&lt;span class="normal"&gt;3&lt;/span&gt;
&lt;span class="normal"&gt;4&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/td&gt;&lt;td class="code"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="kd"&gt;let&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;mut&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="fm"&gt;vec!&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="k"&gt;u32&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="k"&gt;u32&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="kd"&gt;let&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="k"&gt;mut&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="kd"&gt;let&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="k"&gt;mut&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="c1"&gt;// do stuff with a, b&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;p&gt;The trouble is, Rust only knows the type signature, which here is something like &lt;code&gt;index_mut(&amp;amp;'a mut self, index: usize) -&amp;gt; &amp;amp;'a T&lt;/code&gt;.  Nothing about that says that you&amp;#8217;re borrowing distinct elements rather than some core part of the type — and, in fact, the above code is only safe &lt;em&gt;because&lt;/em&gt; you&amp;#8217;re borrowing distinct elements.  In the general case, Rust can&amp;#8217;t possibly know that.  It seems obvious enough from the different indexes, but nothing about the type system even says that different indexes have to return different values.  And what if one were borrowed as &lt;code&gt;&amp;amp;mut v[1]&lt;/code&gt; and the other were borrowed with &lt;code&gt;v.iter_mut().next().unwrap()&lt;/code&gt;?&lt;/p&gt;
&lt;p&gt;Anyway, this is exactly where people start to turn to &lt;a href="https://doc.rust-lang.org/std/cell/struct.RefCell.html"&gt;&lt;code&gt;RefCell&lt;/code&gt;&lt;/a&gt; — if you&amp;#8217;re very sure you know better than Rust, then a &lt;code&gt;RefCell&lt;/code&gt; will skirt the borrow checker while still enforcing &lt;em&gt;at runtime&lt;/em&gt; that you don&amp;#8217;t have more than one mutable borrow at a&amp;nbsp;time.&lt;/p&gt;
&lt;p&gt;But half the lines in this algorithm examine the endpoints of a segment!  I don&amp;#8217;t want to wrap the whole thing in a &lt;code&gt;RefCell&lt;/code&gt;, or I&amp;#8217;ll have to say this&amp;nbsp;everywhere:&lt;/p&gt;
&lt;table class="highlighttable"&gt;&lt;tr&gt;&lt;td class="linenos"&gt;&lt;div class="linenodiv"&gt;&lt;pre&gt;&lt;span class="normal"&gt;1&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/td&gt;&lt;td class="code"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;segment1&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;borrow&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="n"&gt;point&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;segment2&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;borrow&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="n"&gt;point&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;..&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;p&gt;Gross.&lt;/p&gt;
&lt;p&gt;But wait — this code only mutates the points themselves in one place.  When a segment is split, the original segment becomes the left half, and a new segment is created to be the right half.  There&amp;#8217;s no compelling &lt;em&gt;need&lt;/em&gt; for this; it saves an allocation for the left half, but it&amp;#8217;s not critical to the&amp;nbsp;algorithm.&lt;/p&gt;
&lt;p&gt;Thus, I settled on a compromise.  My segment type now looks like&amp;nbsp;this:&lt;/p&gt;
&lt;table class="highlighttable"&gt;&lt;tr&gt;&lt;td class="linenos"&gt;&lt;div class="linenodiv"&gt;&lt;pre&gt;&lt;span class="normal"&gt; 1&lt;/span&gt;
&lt;span class="normal"&gt; 2&lt;/span&gt;
&lt;span class="normal"&gt; 3&lt;/span&gt;
&lt;span class="normal"&gt; 4&lt;/span&gt;
&lt;span class="normal"&gt; 5&lt;/span&gt;
&lt;span class="normal"&gt; 6&lt;/span&gt;
&lt;span class="normal"&gt; 7&lt;/span&gt;
&lt;span class="normal"&gt; 8&lt;/span&gt;
&lt;span class="normal"&gt; 9&lt;/span&gt;
&lt;span class="normal"&gt;10&lt;/span&gt;
&lt;span class="normal"&gt;11&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/td&gt;&lt;td class="code"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="k"&gt;struct&lt;/span&gt; &lt;span class="nc"&gt;SegmentPacket&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="c1"&gt;// a bunch of flags and whatnot used in the algorithm&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="k"&gt;struct&lt;/span&gt; &lt;span class="nc"&gt;SweepSegment&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;left_point&lt;/span&gt;: &lt;span class="nc"&gt;MapPoint&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;right_point&lt;/span&gt;: &lt;span class="nc"&gt;MapPoint&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;faces_outwards&lt;/span&gt;: &lt;span class="kt"&gt;bool&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;index&lt;/span&gt;: &lt;span class="kt"&gt;usize&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;order&lt;/span&gt;: &lt;span class="kt"&gt;usize&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;packet&lt;/span&gt;: &lt;span class="nc"&gt;RefCell&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;SegmentPacket&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;p&gt;I do still need to call &lt;code&gt;.borrow()&lt;/code&gt; or &lt;code&gt;.borrow_mut()&lt;/code&gt; to get at the stuff in the &amp;#8220;packet&amp;#8221;, but that&amp;#8217;s far less common, so there&amp;#8217;s less noise overall.  And I don&amp;#8217;t need to wrap it in &lt;code&gt;Rc&lt;/code&gt; because it&amp;#8217;s part of a type that&amp;#8217;s allocated in the arena and passed around only via&amp;nbsp;references.&lt;/p&gt;
&lt;hr /&gt;
&lt;p&gt;This still leaves me with the problem of how to actually perform the&amp;nbsp;splits.&lt;/p&gt;
&lt;p&gt;I&amp;#8217;m not especially happy with what I came up with, I don&amp;#8217;t know if I can defend it, and I suspect I could do much better.  I changed &lt;code&gt;possibleIntersection&lt;/code&gt; so that rather than performing splits, it &lt;em&gt;returns&lt;/em&gt; the points at which each segment needs splitting, in the form &lt;code&gt;(usize, Option&amp;lt;MapPoint&amp;gt;, Option&amp;lt;MapPoint&amp;gt;)&lt;/code&gt;.  (The &lt;code&gt;usize&lt;/code&gt; is used as a flag for calling code and oughta be an enum, but, isn&amp;#8217;t&amp;nbsp;yet.)&lt;/p&gt;
&lt;p&gt;Now the top-level function is responsible for &lt;em&gt;all&lt;/em&gt; arena management, and all is&amp;nbsp;well.&lt;/p&gt;
&lt;p&gt;Except, er.  &lt;code&gt;possibleIntersection&lt;/code&gt; is called multiple times, and I don&amp;#8217;t want to copy-paste a dozen lines of split code after each call.  I tried putting just that code in its own function, which had the world&amp;#8217;s most godawful signature, and that didn&amp;#8217;t work because&amp;#8230;  uh&amp;#8230;  hm.  I can&amp;#8217;t remember why, exactly!  Should&amp;#8217;ve written that&amp;nbsp;down.&lt;/p&gt;
&lt;p&gt;I tried a local closure next, but closures capture their environment by reference, so now I had references to a bunch of locals for as long as the closure existed, which meant I couldn&amp;#8217;t &lt;em&gt;mutate&lt;/em&gt; those locals.  Argh.  (This seems a little silly to me, since the closure&amp;#8217;s references cannot possibly be used for anything if the closure isn&amp;#8217;t being &lt;em&gt;called&lt;/em&gt;, but maybe I&amp;#8217;m missing something.  Or maybe this is just a limitation of&amp;nbsp;lifetimes.)&lt;/p&gt;
&lt;p&gt;Increasingly desperate, I tried using a macro.  But&amp;#8230;  macros are &lt;em&gt;hygienic&lt;/em&gt;, which means that any new name you use inside a macro is different from any name outside that macro.  The macro thus could not see any of my locals.  Usually that&amp;#8217;s good, but here I explicitly &lt;em&gt;wanted&lt;/em&gt; the macro to mess with my&amp;nbsp;locals.&lt;/p&gt;
&lt;p&gt;I was just about to give up and go live as a hermit in a cabin in the woods, when I discovered something quite incredible.  You can define &lt;em&gt;local macros&lt;/em&gt;!  If you define a macro inside a function, then it can see any locals defined earlier in that function.&amp;nbsp;Perfect!&lt;/p&gt;
&lt;table class="highlighttable"&gt;&lt;tr&gt;&lt;td class="linenos"&gt;&lt;div class="linenodiv"&gt;&lt;pre&gt;&lt;span class="normal"&gt; 1&lt;/span&gt;
&lt;span class="normal"&gt; 2&lt;/span&gt;
&lt;span class="normal"&gt; 3&lt;/span&gt;
&lt;span class="normal"&gt; 4&lt;/span&gt;
&lt;span class="normal"&gt; 5&lt;/span&gt;
&lt;span class="normal"&gt; 6&lt;/span&gt;
&lt;span class="normal"&gt; 7&lt;/span&gt;
&lt;span class="normal"&gt; 8&lt;/span&gt;
&lt;span class="normal"&gt; 9&lt;/span&gt;
&lt;span class="normal"&gt;10&lt;/span&gt;
&lt;span class="normal"&gt;11&lt;/span&gt;
&lt;span class="normal"&gt;12&lt;/span&gt;
&lt;span class="normal"&gt;13&lt;/span&gt;
&lt;span class="normal"&gt;14&lt;/span&gt;
&lt;span class="normal"&gt;15&lt;/span&gt;
&lt;span class="normal"&gt;16&lt;/span&gt;
&lt;span class="normal"&gt;17&lt;/span&gt;
&lt;span class="normal"&gt;18&lt;/span&gt;
&lt;span class="normal"&gt;19&lt;/span&gt;
&lt;span class="normal"&gt;20&lt;/span&gt;
&lt;span class="normal"&gt;21&lt;/span&gt;
&lt;span class="normal"&gt;22&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/td&gt;&lt;td class="code"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="fm"&gt;macro_rules!&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;_split_segment&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="cp"&gt;$seg&lt;/span&gt;:&lt;span class="nc"&gt;expr&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="cp"&gt;$pt&lt;/span&gt;:&lt;span class="nc"&gt;expr&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="kd"&gt;let&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;pt&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="cp"&gt;$pt&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="kd"&gt;let&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;seg&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="cp"&gt;$seg&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="c1"&gt;// ... waaay too much code ...&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;

&lt;span class="k"&gt;loop&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="c1"&gt;// ...&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="c1"&gt;// This is possibleIntersection, renamed because Rust rightfully complains about camelCase&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="kd"&gt;let&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;cross&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;handle_intersections&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;Some&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;segment&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;maybe_above&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kd"&gt;let&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;Some&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;pt&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;cross&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="n"&gt;segment&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;_split_segment&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;segment&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;pt&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kd"&gt;let&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;Some&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;pt&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;cross&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="n"&gt;maybe_above&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;Some&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;_split_segment&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;maybe_above&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;unwrap&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;pt&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="c1"&gt;// ...&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;p&gt;(This doesn&amp;#8217;t actually quite match the original algorithm, which has one case where a segment can be split &lt;em&gt;twice&lt;/em&gt;.  I realized that I could just do the left-most split, and a later iteration would perform the other split.  I sure hope that&amp;#8217;s right,&amp;nbsp;anyway.)&lt;/p&gt;
&lt;p&gt;It&amp;#8217;s &lt;em&gt;a bit&lt;/em&gt; ugly, and I ran into a whole lot of implicit behavior from the C++ code that I had to fix — for example, the segment is sometimes mutated just before it&amp;#8217;s split, purely as a shortcut for mutating the left part of the split.  But it finally compiles!  And runs!  And kinda worked, a&amp;nbsp;bit!&lt;/p&gt;
&lt;h2 id="aftermath"&gt;&lt;a class="toclink" href="#aftermath"&gt;Aftermath&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;I still had a lot of work to&amp;nbsp;do.&lt;/p&gt;
&lt;p&gt;For one, this code was designed for intersecting two shapes, not mass-intersecting a big pile of shapes.  The basic algorithm doesn&amp;#8217;t care about how many polygons you start with — all it sees is segments — but the code for constructing the return value needed some heavy&amp;nbsp;modification.&lt;/p&gt;
&lt;p&gt;The biggest change by far?  The original code traced each &lt;em&gt;segment&lt;/em&gt; once, expecting the result to be only a single shape.  I had to change that to trace each &lt;em&gt;side&lt;/em&gt; of each segment once, since the vast bulk of the output consists of shapes which share a side.  This violated a few assumptions, which I had to hack&amp;nbsp;around.&lt;/p&gt;
&lt;p&gt;I also ran into a couple very bad edge cases, spent ages debugging them, then found out that the original algorithm had a subtle workaround that I&amp;#8217;d commented out because it was awkward to port but didn&amp;#8217;t seem to do anything.&amp;nbsp;Whoops!&lt;/p&gt;
&lt;p&gt;The worst was a precision error, where a vertical line could be split on a point not &lt;em&gt;quite&lt;/em&gt; actually &lt;em&gt;on&lt;/em&gt; the line, which wreaked all kinds of havoc.  I worked around that with some tasteful rounding, which is highly dubious but makes the output more appealing to my squishy human brain.  (I might switch to the original workaround, but I really dislike that even simple cases can spit out points at 1500.0000000000003.  The whole thing is parameterized over the coordinate type, so maybe I could throw a rational type in there and cross my&amp;nbsp;fingers?)&lt;/p&gt;
&lt;p&gt;All that done, I finally, &lt;em&gt;finally&lt;/em&gt;, after a couple months of intermittent progress, got what I&amp;nbsp;wanted!&lt;/p&gt;
&lt;div class="prose-full-illustration"&gt;
&lt;img src="https://eev.ee/media/2018-03-30-rust-adventure/map01-reachability.png" alt=""&gt;
&lt;/div&gt;

&lt;p&gt;This is Doom 2&amp;#8217;s &lt;span class="caps"&gt;MAP01&lt;/span&gt;.  The black area to the left of center is where the player starts.  Gray areas indicate where the player can walk from there, with lighter shades indicating more distant areas, where &amp;#8220;distance&amp;#8221; is measured by the minimum number of line crossings.  Red areas can&amp;#8217;t be reached at&amp;nbsp;all.&lt;/p&gt;
&lt;p&gt;(Note: large playable chunks of the map, including the exit room, are red.  That&amp;#8217;s because those areas are behind doors, and this code doesn&amp;#8217;t understand doors&amp;nbsp;yet.)&lt;/p&gt;
&lt;p&gt;(Also note: The big crescent in the lower-right is also black because I was lazy and looked for the player&amp;#8217;s starting sector by checking the bbox, and that sector&amp;#8217;s bbox happens to&amp;nbsp;match.)&lt;/p&gt;
&lt;p&gt;The code that generated this had to go out of its way to delete all the unreachable zones around solid walls.  I think I could modify the algorithm to do that on the fly pretty easily, which would probably speed it up a bit too.  Downside is that the algorithm would then be pretty specifically tied to this problem, and not usable for any other kind of polygon intersection, which I would think could come up elsewhere?  The modifications would be pretty minor, though, so maybe I could confine them to a closure or&amp;nbsp;something.&lt;/p&gt;
&lt;h2 id="some-final-observations"&gt;&lt;a class="toclink" href="#some-final-observations"&gt;Some final&amp;nbsp;observations&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;It runs surprisingly slowly.  Like, multiple seconds.  Unless I add &lt;code&gt;--release&lt;/code&gt;, which speeds it up by a factor of&amp;#8230;  some number with multiple digits.  Wahoo.  Debug mode has a high price, especially with a lot of calls in&amp;nbsp;play.&lt;/p&gt;
&lt;p&gt;The current state of this code is &lt;a href="https://github.com/eevee/idchoppers/blob/master/src/shapeops.rs"&gt;on GitHub&lt;/a&gt;.  Please don&amp;#8217;t look at it.  I&amp;#8217;m very&amp;nbsp;sorry.&lt;/p&gt;
&lt;p&gt;Honestly, most of my anguish came not from Rust, but from the original code relying on lots of fairly subtle behavior without bothering to explain what it was doing or even hint that anything unusual was going on.  God, I hate&amp;nbsp;C++.&lt;/p&gt;
&lt;p&gt;I don&amp;#8217;t know if the Rust community can learn from this.  I don&amp;#8217;t know if I even learned from this.  Let&amp;#8217;s all just quietly forget about&amp;nbsp;it.&lt;/p&gt;
&lt;p&gt;Now I just need to figure &lt;a href="https://play.rust-lang.org/?gist=57660ea68fa3ab7589fd3c8e266a6fb8&amp;amp;version=nightly"&gt;&lt;em&gt;this&lt;/em&gt;&lt;/a&gt; one&amp;nbsp;out&amp;#8230;&lt;/p&gt;</content><category term="blog"></category><category term="tech"></category><category term="patreon"></category><category term="rust"></category></entry></feed>