<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom"><title>fuzzy notepad - articles</title><link href="https://eev.ee/" rel="alternate"></link><link href="https://eev.ee/feeds/articles.atom.xml" rel="self"></link><id>https://eev.ee/</id><updated>2025-07-03T17:26:00-07:00</updated><entry><title>The rise of Whatever</title><link href="https://eev.ee/blog/2025/07/03/the-rise-of-whatever/" rel="alternate"></link><published>2025-07-03T17:26:00-07:00</published><updated>2025-07-03T17:26:00-07:00</updated><author><name>Eevee</name></author><id>tag:eev.ee,2025-07-03:/blog/2025/07/03/the-rise-of-whatever/</id><summary type="html">&lt;p&gt;This was originally titled “I miss when computers were fun”.  But in the course of writing it, I discovered that there is a &lt;em&gt;reason&lt;/em&gt; computers became less fun, a dark thread woven through a number of events in recent history.&lt;/p&gt;
&lt;p&gt;Let me back up a bit.&lt;/p&gt;
</summary><content type="html">&lt;p&gt;This was originally titled &amp;#8220;I miss when computers were fun&amp;#8221;.  But in the course of writing it, I discovered that there is a &lt;em&gt;reason&lt;/em&gt; computers became less fun, a dark thread woven through a number of events in recent&amp;nbsp;history.&lt;/p&gt;
&lt;p&gt;Let me back up a&amp;nbsp;bit.&lt;/p&gt;


&lt;h2 id="bitcoin"&gt;&lt;a class="toclink" href="#bitcoin"&gt;Bitcoin&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Back in the 00&amp;#8217;s, if you wanted to move money between arbitrary people over the Internet, you realistically had one option:&amp;nbsp;PayPal.&lt;/p&gt;
&lt;aside class="aside--well-actually"&gt;
&lt;p&gt;Either that, or live in some futuristic utopia like the EU where banks consider "send money to people" to be core functionality.  But here in the good ol' U S of A, where material progress requires significant amounts of kicking and screaming, you had PayPal.&lt;/p&gt;
&lt;/aside&gt;
&lt;p&gt;The thing about PayPal is that it holds onto your money, but it isn&amp;#8217;t a bank?  I do not fully appreciate the architecture or its implications here, but PayPal&amp;#8217;s point of view seems to have always been that they can do whatever they want.  They&amp;#8217;ve always been pretty fussy about the use of PayPal to facilitate commissioning artists for drawings of unicorn wieners, for example, so if they thought you were doing that, they would just lock your account and also keep all your money for six months.  For safekeeping, I guess.  And&amp;nbsp;interest.&lt;/p&gt;
&lt;p&gt;Yet PayPal was the only option for many rinky-dink individuals selling one-off goods and services, so there was some amount of frustration that the &lt;em&gt;only available middleman&lt;/em&gt; had exclusive right to say how you were allowed to spend your money, or what kind of indie business you were allowed to run.  And if they caught you ignoring the rules then they got to keep your money for half a&amp;nbsp;year.&lt;/p&gt;
&lt;p&gt;And then in 2010 or so, I heard about Bitcoin.  And it sounded like the wave of the future.  Finally, a way to just &lt;em&gt;send money to someone&lt;/em&gt;.  What a fucking concept.  And imagine what you could build with such a system!  Websites could have real tip jars.  Browsers could have tipping built right in that transfer only a few cents, since transactions would be so&amp;nbsp;effortless.&lt;/p&gt;
&lt;p&gt;I downloaded a miner (well, &lt;em&gt;the&lt;/em&gt; miner at the time, I think) and ran it for like a day and failed to mine a coin.  There was nothing else to really do, so I closed it and forgot all about&amp;nbsp;Bitcoin.&lt;/p&gt;
&lt;p&gt;Fast forward a bit, Bitcoin has reached mainstream awareness, and&amp;#8230;  none of that stuff happened.  Bitcoin is not so much a currency as it is an entire &lt;em&gt;ecosystem&lt;/em&gt; of schemes.  The only mention I&amp;#8217;ve heard in the last year of being able to actually &lt;em&gt;buy&lt;/em&gt; anything with Bitcoin was gray market estradiol.  (Even gray market &lt;span class="caps"&gt;FIP&lt;/span&gt; medication just takes credit cards!)  The only browser with built-in tipping is the one spearheaded by a man whose other claims to fame are inventing JavaScript and wanting to outlaw my marriage, and the token it uses apparently had 80 whole sellers in the past 24 hours.  Sounds like all of that is going&amp;nbsp;great.&lt;/p&gt;
&lt;p&gt;Meanwhile, fifteen years later, the state of the art in sending arbitrary people money seems to be&amp;#8230;  uh, PayPal.  But now we have Stripe, too, which can take credit card payments if you know how to make a website that uses it, but which &lt;em&gt;also&lt;/em&gt; forbids drawings of unicorn wieners.  Patreon?  Stripe and PayPal.  Itch?  Stripe and PayPal.  Ko-fi?  Stripe and PayPal.  Nothing is fundamentally&amp;nbsp;different.&lt;/p&gt;
&lt;p&gt;But the dream has died.  It almost came true, and then it was immediately co-opted by a bunch of get-rich-quick grifters and a bunch of turbo-libertarians whose entire identities are defined by the Things that they Own and who want to cryptographically impose that on everyone else too because they&amp;#8217;re mad that World of Warcraft nerfed warlock or&amp;nbsp;something.&lt;/p&gt;
&lt;p&gt;And I suspect the core problem that has wended its way through the history of cryptocurrency is that the vast majority of people involved &lt;em&gt;do not actually care&lt;/em&gt; what the thing they&amp;#8217;re flocking to is.  What they care about is that it has a graph, and that they get rich if the graph goes up, so they say whatever might make the graph go up.  The graph even looks exactly the same for every coin and &lt;span class="caps"&gt;NFT&lt;/span&gt; and Whatever else: x-axis time, y-axis dollars.  The only place the &lt;em&gt;thing&lt;/em&gt; appears at all is in the title, where you can safely ignore&amp;nbsp;it.&lt;/p&gt;
&lt;p&gt;Plenty of people will talk up the supposed benefits of their pet thingamajig, of course, but my suspicion is that many of them don&amp;#8217;t actually care that much.  They have a &lt;em&gt;vested interest&lt;/em&gt; in getting other people to buy into the thing, Whatever the thing may be, because then graph go&amp;nbsp;up.&lt;/p&gt;
&lt;p&gt;And so you have what I can only call a culture of &lt;strong&gt;Whatever&lt;/strong&gt;.  Bitcoin failed as a currency because the people who got most invested in it &lt;em&gt;do not care&lt;/em&gt; about currency — it could be bottled dragon farts for all they care, except that putting it on the computer means there&amp;#8217;s no need to actually worry about a product.  It&amp;#8217;s just something to pump the value of; the underlying asset could be, well, Whatever.  And Bitcoin itself is open source, so you can copy it and make your very own coin, your very own Whatever.  With NFTs, you can make an entire &lt;em&gt;family&lt;/em&gt; of &amp;#8220;collectible&amp;#8221; Whatevers — a strange descriptor given that you can&amp;#8217;t actually collect one of each of them, but who really cares if the description makes sense?  It doesn&amp;#8217;t matter what the art is, or how the technology works, or what the tokens are attached to.  It just has to be something you can convince other people to buy.  The actual thing can be&amp;nbsp;Whatever.&lt;/p&gt;
&lt;p&gt;I think this adequately explains why the proliferation of these guys helped suck all the air out of Twitter.  Tens of thousands of grifters lining every sidewalk, each one passionately hawking an indistinguishable Whatever that they don&amp;#8217;t actually care about.  Endless, &lt;em&gt;endless&lt;/em&gt; fake enthusiasm from people all trying to convince each other to buy into their boilerplate box of nothing.  Buy &lt;em&gt;my&lt;/em&gt; thing!  Haha no don&amp;#8217;t worry about how much of it &lt;em&gt;I&lt;/em&gt; own — let&amp;#8217;s talk about how much of it &lt;em&gt;you&lt;/em&gt; should own!  Hint: it&amp;#8217;s a&amp;nbsp;lot!&lt;/p&gt;
&lt;p&gt;Kind of a&amp;nbsp;bummer.&lt;/p&gt;
&lt;h2 id="the-shape-of-the-web"&gt;&lt;a class="toclink" href="#the-shape-of-the-web"&gt;The shape of the Web&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;The Web is a cool thing because anyone can just put stuff on it.  It is the largest town square bulletin board ever devised.  Back in the day, your &lt;span class="caps"&gt;ISP&lt;/span&gt; would even give you your own website!  I don&amp;#8217;t think they do that so much any more, but there are more cheap or free options than ever — hell, you can host a little website on&amp;nbsp;GitHub.&lt;/p&gt;
&lt;p&gt;And it used to mostly consist of little things made by people, and that was pretty cool!  You would see &lt;em&gt;more than four websites in a day&lt;/em&gt;.  Websites would have &lt;em&gt;colors&lt;/em&gt;!  They wouldn&amp;#8217;t all be designed for a three-inch-wide screen and then just scaled up when you&amp;#8217;re at your desk!  Twitter once let you set your own background image for when people looked at your&amp;nbsp;profile.&lt;/p&gt;
&lt;p&gt;But the trouble with everyone having a bunch of websites is that you lost track of them all and you didn&amp;#8217;t really know when they updated and it was hard to talk &lt;em&gt;back to&lt;/em&gt; a website.  Also, making your own website is kinda hard?  You have to, like, learn&amp;nbsp;things.&lt;/p&gt;
&lt;p&gt;And so the entire Web sort of congealed around a tiny handful of gigantic platforms that &lt;em&gt;everyone on the fucking planet&lt;/em&gt; is on at once.  Sometimes there is some sort of partitioning, like Reddit.  Sometimes there is not, like&amp;nbsp;Twitter.&lt;/p&gt;
&lt;p&gt;That&amp;#8217;s&amp;#8230;  fine, I guess.  Things centralize.  It happens.  You don&amp;#8217;t get tubgirl spam raids so much any more, at&amp;nbsp;least.&lt;/p&gt;
&lt;p&gt;But the centralization poses a problem.  See, the Web is free to &lt;em&gt;look at&lt;/em&gt; (by default), but costs money to &lt;em&gt;host&lt;/em&gt;.  There are free hosts, yes, but those are for static things getting like a thousand visitors a day, not interactive platforms serving &lt;em&gt;a hundred million&lt;/em&gt;.  That starts to cost a bit.  Picture logs being shoveled into a steam engine&amp;#8217;s firebox, except it&amp;#8217;s bundles of cash being shoveled into&amp;#8230;  the&amp;#8230;  uh&amp;#8230;  website&amp;nbsp;hole.&lt;/p&gt;
&lt;p&gt;Traditionally, the way to pay for keeping your website online has been to slather it in ads and suffer the humiliation of Pepsi trying to sell Pepsi halfway down your page.  Ads don&amp;#8217;t pay very much, but for a moderate-size endeavor, that&amp;#8217;s fine.  You write your article and put an ad on it and make twenty cents a month or whatever.  I don&amp;#8217;t know, I don&amp;#8217;t run ads because they&amp;#8217;re an embarrassing blight that make everything they touch&amp;nbsp;worse.&lt;/p&gt;
&lt;p&gt;Together, these forces push big platforms in a very specific direction: &lt;em&gt;maximize how many ads people see&lt;/em&gt;.  To the exclusion of just about anything else.  So Engagement becomes king — it&amp;#8217;s okay if your users are &lt;em&gt;miserable&lt;/em&gt;, so long as they&amp;#8217;re &lt;em&gt;here&lt;/em&gt;.  It&amp;#8217;s okay if the ads are &lt;em&gt;obnoxious&lt;/em&gt;, as long as they&amp;#8217;re &lt;em&gt;seen&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;Then this model spread into phone software.  And then into videography.  And then, somehow, into fucking,&amp;nbsp;Windows??&lt;/p&gt;
&lt;p&gt;And when the primary focus of the business is on the &lt;em&gt;ads&lt;/em&gt;, everything else is sort of ancillary — it&amp;#8217;s only important insofar as it keeps people around, to look at the ads.  It&amp;#8217;s jingling keys.  It&amp;#8217;s&amp;#8230;&amp;nbsp;Whatever.&lt;/p&gt;
&lt;aside class="aside--fascinating-tangent"&gt;
&lt;p&gt;This is why I absolutely cannot fucking stand &lt;em&gt;creative work&lt;/em&gt; being referred to as "content".  "Content" is how you refer to the stuff on a website when you're designing the layout and don't know what actually goes on the page yet.  "Content" is how you refer to the collection of odds and ends in your car's trunk.  "Content" is what marketers call &lt;em&gt;the stuff that goes around the ads&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;"Content"...  is Whatever.&lt;/p&gt;
&lt;/aside&gt;
&lt;p&gt;This is the driving force behind clickbait, behind thumbnails of white guys making 8O faces, behind red arrows, behind video essayists who just read Wikipedia at you three times a week like clockwork, behind suggestion algorithms, behind recipe blogs that all look the same and have a mile of filler fluff, behind video game websites abandoning the idea of articles and instead turning into &lt;span class="caps"&gt;SEO&lt;/span&gt; vultures with inexplicably lengthy articles telling you &amp;#8220;the blue key is under a rock by the river&amp;#8221; so they have more paragraph breaks to put ads between, behind TikTok&amp;#8217;s model of being a constant stream which I have to only guess at because I have never had any interest in TikTok but I assume it&amp;#8217;s a worse version of YouTube Shorts and I already find &lt;em&gt;those&lt;/em&gt; pretty&amp;nbsp;irritating.&lt;/p&gt;
&lt;p&gt;It&amp;#8217;s all the same&amp;nbsp;thing.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Look at it.  Look at it, you stupid baby.  Look how outlandish or shocking or extreme or dramatic, Whatever it is.  Just shut up and look at it, so Home Depot will give me a quarter of a tenth of a&amp;nbsp;cent.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;At least when I write a lot, you know it&amp;#8217;s because I wanted to write it.  Also I&amp;#8217;m probably not lying to you because someone paid me to do&amp;nbsp;it!&lt;/p&gt;
&lt;p&gt;And the only real hope I have here is that someday, maybe, Bitcoin will be a currency, and circulating money around won&amp;#8217;t be the exclusive purview of Froot Loops.&amp;nbsp;Christ.&lt;/p&gt;
&lt;p&gt;Did you know there were entire get-rich-quick schemes about this?  It&amp;#8217;s like &lt;a href="https://www.youtube.com/watch?v=biYciU1uiUw"&gt;writing fake novels&lt;/a&gt;.  Just make a website with a generic WordPress theme (every website looks the same anyway), write a bunch of bland nothing articles about things that seem a &lt;em&gt;little&lt;/em&gt; obscure, and slather it in Google ads.  Then let the money roll in from people accidentally finding your website and leaving when they find out it&amp;#8217;s useless.  But it&amp;#8217;s too late because you already got the ad&amp;nbsp;view!&lt;/p&gt;
&lt;p&gt;I say &amp;#8220;were&amp;#8221; because bothering to &lt;em&gt;write&lt;/em&gt; generic filler about nothing is passé — now the computer can do it for&amp;nbsp;you!&lt;/p&gt;
&lt;h2 id="artificial-reality"&gt;&lt;a class="toclink" href="#artificial-reality"&gt;Artificial reality&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;If you told me ten years ago that by 2025 we&amp;#8217;d have the Star Trek computer, I would&amp;#8217;ve been &lt;em&gt;ecstatic&lt;/em&gt;.  How fucking cool is that!  You talk to your computer and it does&amp;nbsp;things!&lt;/p&gt;
&lt;p&gt;But we didn&amp;#8217;t really get that.  We got, I guess, sparkling autocomplete — a fancy chatbot that can string words together in the most inoffensive people-pleasing customer-service voice you&amp;#8217;ve ever&amp;nbsp;heard.&lt;/p&gt;
&lt;p&gt;The result is something I adamantly do not want to interact with.  I do not want to be exposed to &lt;span class="caps"&gt;LLM&lt;/span&gt; output at any time.  It&amp;#8217;s &lt;em&gt;noise&lt;/em&gt;, and I feel like I get a little dumber every time I accidentally start reading it.  My brain is already a bit glitchy, and I really cannot afford to have it work even more less&amp;nbsp;good.&lt;/p&gt;
&lt;p&gt;And speaking of things that work even more less good, the technology&amp;#8230;  sucks?  It fundamentally doesn&amp;#8217;t do the thing that its investors and diehard fans say it does.  It just strings together text that is statistically plausible.  And every new alleged advancement comes with some invested airhead billionaire boasting about how the computer is as smart as a Ph.D holder now, and then you see the output and it&amp;#8217;s still the most generic banal brain-rotting sludge you&amp;#8217;ve ever seen in your&amp;nbsp;life.&lt;/p&gt;
&lt;p&gt;Most of my exposure to &lt;span class="caps"&gt;LLM&lt;/span&gt; output is via Google cramming it everywhere they can think of, and in every instance the result is &lt;em&gt;worse&lt;/em&gt;.  Google Search keeps redesigning its way around my μBlock filters to dedicate an entire third of my desktop screen height to an &amp;#8220;&lt;span class="caps"&gt;AI&lt;/span&gt; summary&amp;#8221; — which either lightly restates the highlighted part of the top search result anyway, or is just total bullshit.  YouTube keeps showing a sprinkling of &amp;#8220;&lt;span class="caps"&gt;AI&lt;/span&gt; summaries&amp;#8221; under video thumbnails that, without fail, restate the video title in more words.  My phone&amp;#8217;s fucking &lt;em&gt;weather app&lt;/em&gt; has an &amp;#8220;&lt;span class="caps"&gt;AI&lt;/span&gt; summary&amp;#8221; with incredible insights like &amp;#8220;it&amp;#8217;ll get warmer over the course of the week&amp;#8221;, which I could readily see for myself if this block of white noise weren&amp;#8217;t pushing the &lt;em&gt;temperature graph&lt;/em&gt; off the bottom of the screen.  Over and over, &lt;em&gt;actual information&lt;/em&gt; is moved out of the way to make room for an unreliable lossy compression of that information into text that takes longer to&amp;nbsp;read.&lt;/p&gt;
&lt;p&gt;But this is worth billions of&amp;nbsp;dollars.&lt;/p&gt;
&lt;aside&gt;
&lt;p&gt;If you are unfortunate enough to have a recent Pixel phone, it's at least possible to reliably turn all this crap off — go to System &amp;gt; Apps &amp;gt; Show all apps, use the three-dots menu to "Show system", tap "AICore", and disable it.  There used to be more bits and pieces to turn off, but I guess Android 16 consolidated them?  Anyway I've seen nothing resembling LLM output since doing this.&lt;/p&gt;
&lt;/aside&gt;
&lt;p&gt;I think what really gets me here, and what no one really talks about, is that the bar has been revealed to be so low.  &lt;span class="caps"&gt;LLM&lt;/span&gt; features get bolted onto fucking &lt;em&gt;everything&lt;/em&gt; because what they do, what they really do, at their core, is this: &lt;strong&gt;Whatever&lt;/strong&gt;.  They do Whatever.  And that&amp;#8217;s great, because Whatever is &lt;em&gt;something&lt;/em&gt;.  There&amp;#8217;s no such thing as an error, no empty results page, no such thing as a missing feature or an uncovered case.  Almost without fail, you&amp;#8217;ll get &lt;em&gt;something&lt;/em&gt;.  Is it useful?  Is it correct?  Is it remotely based in reality?  Who cares?  Far more important is that there is &lt;em&gt;output&lt;/em&gt;.  Whatever is apparently better than nothing.  Cheap and inoffensive and disposable, like a red beer cup.  We are doing to the Internet what we already did to the ocean: filling it with a great swirling vortex of&amp;nbsp;trash.&lt;/p&gt;
&lt;h3 id="case-study-1"&gt;&lt;a class="toclink" href="#case-study-1"&gt;Case study 1&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;&lt;span class="dquo"&gt;&amp;#8220;&lt;/span&gt;Ah!&amp;#8221; the Hacker News commenters cry.  &amp;#8220;But have you &lt;em&gt;tried&lt;/em&gt; it?&amp;#8221; they ask with all the indignity of a kindergartener offended that you won&amp;#8217;t eat their mud&amp;nbsp;pie.&lt;/p&gt;
&lt;p&gt;But yes, thanks: I was once offered this challenge when faced with a Ren&amp;#8217;Py problem, so I grit my teeth and posed my question to some &lt;span class="caps"&gt;LLM&lt;/span&gt;.  It confidently listed several related formatting tags that would solve my&amp;nbsp;problem.&lt;/p&gt;
&lt;p&gt;One teeny tiny issue: those tags &lt;strong&gt;did not and had never existed&lt;/strong&gt;.  I typed this additional context into the computer, and it generated a profuse apology followed by a &lt;em&gt;different&lt;/em&gt; set of fictional tags.  That was the end of that grand&amp;nbsp;experiment.&lt;/p&gt;
&lt;p&gt;The trouble was likely that there &lt;em&gt;was&lt;/em&gt; no built-in way to do what I wanted, &lt;em&gt;and&lt;/em&gt; no one had ever successfully done it before, so the machine had nothing to draw from&amp;#8230;  and simply generated something that sounded plausible instead.  Because that is what this technology does: it continues a conversation in a way that &lt;em&gt;sounds plausible&lt;/em&gt;, as defined by similarity to existing conversations.  If there are existing conversations about the topic, great!  That makes for a more specific measure of plausibility.  If not, &lt;em&gt;even better&lt;/em&gt;!  Just about &lt;em&gt;anything&lt;/em&gt; might be plausible!  It can just generate &lt;strong&gt;Whatever&lt;/strong&gt;!&lt;/p&gt;
&lt;p&gt;I cannot stress enough that this is &lt;strong&gt;worse than useless&lt;/strong&gt; to me.  Not only did it not answer my question, but it sent me on a wild goose chase making sure I had not somehow overlooked the fake &lt;span class="caps"&gt;API&lt;/span&gt; it&amp;nbsp;generated.&lt;/p&gt;
&lt;p&gt;Like, just to calibrate here: you know how some code editors will automatically fill in a right bracket or quote when you type a left one?  You&amp;nbsp;type &lt;code&gt;"&lt;/code&gt; and the result&amp;nbsp;is &lt;code&gt;"|"&lt;/code&gt;?  Yeah, that drives me up the wall.  It saves no time whatsoever, and it&amp;#8217;s wrong often enough that I &lt;em&gt;waste&lt;/em&gt; time having to &lt;em&gt;correct&lt;/em&gt; for&amp;nbsp;it.&lt;/p&gt;
&lt;p&gt;And that&amp;#8217;s a predictable operation that inserts a single character!  What we&amp;#8217;ve invented is an entire fake persona that will waste your time entire &lt;em&gt;paragraphs&lt;/em&gt; at&amp;nbsp;once.&lt;/p&gt;
&lt;p&gt;I can&amp;#8217;t imagine using this to do any actual work and I don&amp;#8217;t understand how anyone else does.  This is a whole new kind of failure case we&amp;#8217;ve invented.  I did also ask &lt;em&gt;people&lt;/em&gt; about this problem, and they responded in the ways people might: they said they didn&amp;#8217;t know, or they suggested an elaborate and tedious workaround that would technically solve the problem (but introduce new ones).  But the &lt;span class="caps"&gt;LLM&lt;/span&gt; statistically generated something that &lt;em&gt;sounds like an &lt;span class="caps"&gt;API&lt;/span&gt; that could exist&lt;/em&gt;.  It produced an answer that was plausible, thorough, informative, relevant, and contained &lt;strong&gt;no useful information whatsoever&lt;/strong&gt;.  It produced the &lt;em&gt;opposite&lt;/em&gt; of information!  It produced &lt;strong&gt;noise&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;Why would I want this?  Why would I want to use a machine that sometimes generates text that resembles a person confidently lying to me?  People are sometimes &lt;em&gt;wrong&lt;/em&gt;, sure — that&amp;#8217;s why Stack Overflow has downvotes — but this is something else entirely.  If a real person did this to you, you would stop asking them questions &lt;em&gt;real&lt;/em&gt; fucking&amp;nbsp;fast.&lt;/p&gt;
&lt;p&gt;&lt;span class="caps"&gt;LLM&lt;/span&gt; output is crap.  It&amp;#8217;s just crap.  It sucks, and is&amp;nbsp;bad.&lt;/p&gt;
&lt;p&gt;Anyway I went on to do the thing I wanted regardless, because I&amp;#8217;m a programmer and I know how to make computers do&amp;nbsp;things.&lt;/p&gt;
&lt;aside class="aside--fascinating-tangent"&gt;
&lt;p&gt;It's not really relevant to the story, but the actual problem I had was that I like to put two spaces between sentences, and I wanted Ren'Py to render this extra space.  Unfortunately, Ren'Py collapses all whitespace in strings down to a single space &lt;em&gt;at parse time&lt;/em&gt;, which made that seemingly impossible.  In fact, a formatting tag could not &lt;em&gt;possibly&lt;/em&gt; solve this, because the whitespace collapsing happens to string literals, and formatting happens (much later) to string values.&lt;/p&gt;
&lt;p&gt;So I just monkeypatched the parser.  Like you do.  Ren'Py is wild.&lt;/p&gt;
&lt;/aside&gt;
&lt;p&gt;I mean, I get it.  I was trying to do something that had never been done before.  LLMs are fine at things that appear a zillion times in their training data — in fact, this is probably a big part of the trick, because the things that appear more often in their training data are the things people are more likely to ask about &lt;em&gt;in general&lt;/em&gt; and thus the things people are more likely to ask an &lt;span class="caps"&gt;LLM&lt;/span&gt;.  But whose creative output consists solely of doing things a million people have already done?  Is everyone else working on projects built exclusively out of lists of primes and rebalancing binary&amp;nbsp;trees?&lt;/p&gt;
&lt;h3 id="case-study-2"&gt;&lt;a class="toclink" href="#case-study-2"&gt;Case study 2&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Back in December, I was &lt;a href="https://bsky.app/profile/eev.ee/post/3lclnybocls2n"&gt;complaining about something else&lt;/a&gt; (surprise, it was Web ads!) and just happened to look at the Visual Studio Code website, most of which was devoted to its &lt;span class="caps"&gt;LLM&lt;/span&gt; code-completion service, Copilot.  I don&amp;#8217;t care to desecrate this blog with &lt;span class="caps"&gt;LLM&lt;/span&gt; output — it&amp;#8217;s &lt;a href="https://bsky.app/profile/eev.ee/post/3lclp4y5xe22w"&gt;on Bluesky if you must&lt;/a&gt; — but suffice to say, it wasn&amp;#8217;t &lt;em&gt;great&lt;/em&gt;.  It was a call to a web service, and the generated code failed to encode form data.  You know, Computer 101 stuff.  Also it was like twice as long as it needed to be.  Also it wouldn&amp;#8217;t work on &lt;span class="caps"&gt;HTTPS&lt;/span&gt; websites because the web service&amp;#8217;s certificate expired three years ago — which is a fun footgun, since you very well might be on &lt;span class="caps"&gt;HTTP&lt;/span&gt; localhost, and then it&amp;#8217;ll only break when you go&amp;nbsp;live.&lt;/p&gt;
&lt;p&gt;I found it highly unlikely that the latest and greatest &lt;span class="caps"&gt;API&lt;/span&gt; for &amp;#8220;get website&amp;#8221; couldn&amp;#8217;t just encode form data for you, but lo and behold: it can!  Copilot just didn&amp;#8217;t bother to make use of it.  And since Copilot is a Whatever machine and its answers are these one-time disposable things, there&amp;#8217;s no mechanism for someone else to come in and go &amp;#8220;hey, you forgot to encode the form&amp;nbsp;data&amp;#8221;.&lt;/p&gt;
&lt;p&gt;What even is this thing we&amp;#8217;ve invented?  Stack Overflow, but you only get the answers people scramble to type first so they can get the points?  Oh and they just lie to you sometimes?  Why would I want&amp;nbsp;this?&lt;/p&gt;
&lt;p&gt;And I didn&amp;#8217;t cherry-pick this example!  &lt;em&gt;They chose it!&lt;/em&gt;  This was the front-page example for a state-of-the-art &lt;span class="caps"&gt;LLM&lt;/span&gt; integrated with the most popular code editor in the world, all built by one of the richest companies in human history, whose &lt;em&gt;entire business&lt;/em&gt; is software and who has specifically invested a zillion dollars in &lt;em&gt;this specific technology&lt;/em&gt;.  This is the gizmo &lt;strong&gt;at its best&lt;/strong&gt;!  And it&amp;#8217;s&amp;nbsp;crap!&lt;/p&gt;
&lt;p&gt;But it does &lt;em&gt;something&lt;/em&gt;.  And that&amp;#8217;s what&amp;#8217;s&amp;nbsp;important.&lt;/p&gt;
&lt;h3 id="the-broader-culture"&gt;&lt;a class="toclink" href="#the-broader-culture"&gt;The broader culture&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;There are people who use these, apparently.  And it just feels so&amp;#8230;  depressing.  There are people I once respected who, apparently, don&amp;#8217;t actually enjoy &lt;em&gt;doing the thing&lt;/em&gt;.  They would like to describe what they want and receive Whatever — some beige sludge that vaguely resembles it.  That isn&amp;#8217;t programming, though.  That&amp;#8217;s management, a fairly different job.  I&amp;#8217;m not interested in managing.  I&amp;#8217;m &lt;em&gt;certainly&lt;/em&gt; not interested in managing this bizarre polite lying daydream machine.  It feels like a vizier who has definitely been spending some time plotting my&amp;nbsp;demise.&lt;/p&gt;
&lt;p&gt;It makes programming spaces feel bleaker.  I don&amp;#8217;t want to help someone who opens with &amp;#8220;I don&amp;#8217;t know how to do this so I asked ChatGPT and it gave me these 200 lines but it doesn&amp;#8217;t work&amp;#8221;.  I don&amp;#8217;t want to know how much code wasn&amp;#8217;t actually written by anyone.  I don&amp;#8217;t want to hear how many of my colleagues think Whatever is equivalent to their own output.  I don&amp;#8217;t want to keep watching people fall for a carnival&amp;nbsp;trick.&lt;/p&gt;
&lt;p&gt;A couple days ago I saw someone (whose bio claimed they&amp;#8217;re a Bluesky engineer, but who knows) insist that it&amp;#8217;s &amp;#8220;very stupid&amp;#8221; to &lt;em&gt;not&lt;/em&gt; use a chatbot for programming.  I just cannot comprehend this.  If the task is &lt;em&gt;easy&lt;/em&gt;, I could just write the code about as fast as I could describe it anyway.  If the task is &lt;em&gt;hard&lt;/em&gt;, then it&amp;#8217;s all the more likely the generated code will be subtly wrong (or overtly wrong).  If it&amp;#8217;s something I &lt;em&gt;don&amp;#8217;t know&lt;/em&gt;, I can go &lt;em&gt;find out&lt;/em&gt; about it, and now I know more things.  What are you all even writing that so much of it consists of generic&amp;nbsp;slop?&lt;/p&gt;
&lt;p&gt;But also&amp;#8230;  why do you care?  Why would someone using a really cool tool that makes them more productive&amp;#8230;  feel compelled to sneer and get defensive at the mere &lt;em&gt;suggestion&lt;/em&gt; that someone else isn&amp;#8217;t doing the same?  I know there are people who oppose, say, syntax coloring, and I think that&amp;#8217;s pretty weird, but I don&amp;#8217;t go out of my way to dunk on them.  I can&amp;#8217;t imagine having a stronger reaction than saying &amp;#8220;lmao what&amp;#8221; and immediately forgetting about it.  I might have strong opinions about what &lt;em&gt;code&lt;/em&gt; looks like, because &lt;em&gt;I might have to read it&lt;/em&gt;, but why would I — why would &lt;em&gt;anyone&lt;/em&gt; — have such an intense reaction to the hypothetical editor setup of a hypothetical&amp;nbsp;stranger?&lt;/p&gt;
&lt;p&gt;It feels like the same attitude that happened with Bitcoin, the same smug nose-wrinkling contempt.  &lt;em&gt;Bitcoin is the future.  It&amp;#8217;ll replace the dollar by 2020.  You&amp;#8217;re gonna be left behind.  Enjoy being poor.&lt;/em&gt;  Sure thing, Disco Stu!  There have definitely never been any inventions that turned out to be bad ideas or were just plain forgotten about.  But the Bitcoin people &lt;em&gt;make more money&lt;/em&gt; if they can shame everyone else into buying more Bitcoin, so of course they&amp;#8217;re gonna try to do it.  What do programmers get out of this?  Unless you work at Microsoft and have a &lt;em&gt;lot&lt;/em&gt; of stock options, you aren&amp;#8217;t getting rich off of how many people use&amp;nbsp;Copilot.&lt;/p&gt;
&lt;p&gt;It&amp;#8217;s curiously similar to how, as a fitting segue, Microsoft is now gonna &lt;a href="https://www.businessinsider.com/microsoft-internal-memo-using-ai-no-longer-optional-github-copilot-2025-6"&gt;factor &amp;#8220;&lt;span class="caps"&gt;AI&lt;/span&gt;&amp;#8221; use into employee performance reviews&lt;/a&gt;:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;span class="dquo"&gt;&amp;#8220;&lt;/span&gt;&lt;span class="caps"&gt;AI&lt;/span&gt; is now a fundamental part of how we work,&amp;#8221; Liuson wrote. &amp;#8220;Just like collaboration, data-driven thinking, and effective communication, using &lt;span class="caps"&gt;AI&lt;/span&gt; is no longer optional — it&amp;#8217;s core to every role and every&amp;nbsp;level.&amp;#8221;&lt;/p&gt;
&lt;p&gt;Liuson told managers that &lt;span class="caps"&gt;AI&lt;/span&gt; &amp;#8220;should be part of your holistic reflections on an individual&amp;#8217;s performance and&amp;nbsp;impact.&amp;#8221;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;What are we actually saying here — that even Microsoft has to evaluate usage of &amp;#8220;&lt;span class="caps"&gt;AI&lt;/span&gt;&amp;#8221; directly, because it doesn&amp;#8217;t affect performance enough to have an obvious impact otherwise?  That the technology is so limp that even its biggest investor has to &lt;em&gt;strong-arm its own employees&lt;/em&gt; into using it?  That their own employees don&amp;#8217;t &lt;em&gt;want&lt;/em&gt; to use&amp;nbsp;it?&lt;/p&gt;
&lt;p&gt;Genuinely good new tools don&amp;#8217;t tend to need &lt;em&gt;coercion&lt;/em&gt; to fuel their adoption only a few years into their existence, right?  What the fuck is going on&amp;nbsp;here?&lt;/p&gt;
&lt;hr /&gt;
&lt;p&gt;Another Bluesky quip I saw earlier today, and the reason I picked up writing this post (which I&amp;#8217;d started last&amp;nbsp;week):&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Quitting programming as a career right now because of LLMs would be like quitting carpentry as a career thanks to the invention of the table&amp;nbsp;saw.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;I&amp;#8217;m not trying to put the author on blast or anything, so let&amp;#8217;s leave it anonymous, but — my guy?  My&amp;nbsp;dude?&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;What on earth are you talking&amp;nbsp;about?&lt;/strong&gt;&lt;/p&gt;
&lt;aside class="aside--note-from-future"&gt;
&lt;p&gt;Okay so this was actually intended &lt;a href="https://bsky.app/profile/simonwillison.net/post/3lt4ooecpls2i"&gt;the opposite way from what I thought&lt;/a&gt; because the author has apparently seen it suggested that LLMs are so incredible that programmers should just give up on their careers now (?????), but the "LLMs are just another tool!" thing is &lt;em&gt;absolutely&lt;/em&gt; a sentiment I've seen deployed countless times, so let's pretend someone else said this and meant it as "This is just the latest advancement in the trade!" so that I don't have to completely rewrite this part.  Ironically, I already rewrote it once to fit with this quote!  Anyway sorry Simon oops&lt;/p&gt;
&lt;/aside&gt;
&lt;p&gt;I don&amp;#8217;t know the context for this.  What I &lt;em&gt;do&lt;/em&gt; know is that a table saw quickly cuts straight lines.  That is the thing it does.  It doesn&amp;#8217;t do Whatever.  It doesn&amp;#8217;t sometimes cut wavy lines and sometimes glue pieces together instead.  It doesn&amp;#8217;t roll some dice and guess what shape of cut you are statistically likely to want based on an extensive database of previous cuts.  &lt;em&gt;It cuts a straight fucking&amp;nbsp;line.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;If I &lt;em&gt;were&lt;/em&gt; a carpenter, and my colleagues got really into this new thing where you just chuck 2×4s at a spinning whirling mass of blades until a chair comes out the other side&amp;#8230;  you know, I just might want to switch&amp;nbsp;careers.&lt;/p&gt;
&lt;p&gt;I keep seeing this — people compare LLMs to calculators, or screwdrivers, or digital cameras, or whatever.  I&amp;#8217;m left wondering if the people saying this stuff have ever &lt;em&gt;used&lt;/em&gt; any of those things.  A calculator does arithmetic for you — thus automating the tedious, repetitive part — but you still have to know &lt;em&gt;which buttons to press&lt;/em&gt; to get the answer you want.  You can&amp;#8217;t just type the entire problem in and get Whatever — something that sounds plausible, with a microscopic disclaimer that checking it for accuracy is &lt;em&gt;your&lt;/em&gt;&amp;nbsp;problem.&lt;/p&gt;
&lt;p&gt;Calculators do have limitations at their extremes, and if you&amp;#8217;re working with extremes, you have to be aware of those.  Table saws will (or, used to) cut through fingers just as happily as wood.  Tools have edge cases — at their &lt;em&gt;edges&lt;/em&gt;.  LLMs have edge cases &lt;strong&gt;everywhere&lt;/strong&gt;, and they are constantly changing, even minute to minute, even for exactly the same input fed to exactly the same model.  It&amp;#8217;s also possible to &lt;em&gt;adjust or customize&lt;/em&gt; tools in various ways, whereas 90% of the times I&amp;#8217;ve seen someone talk about their customized &lt;span class="caps"&gt;LLM&lt;/span&gt;, all they&amp;#8217;ve done is prepend a paragraph like &amp;#8220;Please answer as though speaking to a customer.&amp;#8221;  The state of the art is to ask the computer nicely to do something, add a disclaimer saying it&amp;#8217;s not your problem if the computer is racist, and then charge for&amp;nbsp;access.&lt;/p&gt;
&lt;p&gt;This is not mere automation.  This is a completely new type of thing.  We&amp;#8217;ve never had a machine that can take almost any input and just do Whatever.  But I keep watching people act like it&amp;#8217;s the same level of invention as the egg slicer and I feel like I&amp;#8217;m losing my fucking&amp;nbsp;mind.&lt;/p&gt;
&lt;h3 id="but-what-if-it-gets-better"&gt;&lt;a class="toclink" href="#but-what-if-it-gets-better"&gt;But what if it gets better&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;I don&amp;#8217;t know.  What if it does?  What does that &lt;em&gt;mean&lt;/em&gt;?  I hear &amp;#8220;better&amp;#8221; and I read the press release and in the fine print it says that now it can count the number of letters in &amp;#8220;Mississippi&amp;#8221; correctly or whatever.  And then it&amp;#8217;s still&amp;nbsp;crap.&lt;/p&gt;
&lt;p&gt;What if it didn&amp;#8217;t produce crap?  I struggle to imagine such a world, in no small part because the hype around the Whatever machine is so staggeringly overblown.  My phone has a dedicated Tensor™ chip to simulate artificial intelligence in the palm of my hand, wow!  Here&amp;#8217;s what it does: tells me it&amp;#8217;ll be hot this&amp;nbsp;week.&lt;/p&gt;
&lt;p&gt;But if the machine still just fabricates an elaborate plausible fiction when it doesn&amp;#8217;t have an answer on-hand, what good is it?  I can always just go find the place it got the answer from originally, and at least then I know that someone &lt;em&gt;wrote it&lt;/em&gt;.  Someone had a &lt;em&gt;reason&lt;/em&gt; to think it, even if they were mistaken.  Maybe the well is just permanently poisoned — anytime I see anything I know to be &lt;span class="caps"&gt;LLM&lt;/span&gt; output, my first assumption is that it&amp;#8217;s nonsense, completely divorced from&amp;nbsp;reality.&lt;/p&gt;
&lt;p&gt;I know a lot of people have a lot of gripes with LLMs and generative &amp;#8220;&lt;span class="caps"&gt;AI&lt;/span&gt;&amp;#8221; that tie them to big grandiose concerns like intellectual property or environmental impact.  My gripes are more of a tangled web that I can only summarize as: &lt;em&gt;the vibes are bad&lt;/em&gt;.  The tone is unbearable.  The lying as a fallback is offensive.  The advertising keeps focusing on how you can coast through life without caring about your work or family because you can just generate a birthday card or whatever.  The people funding and pushing it keep openly salivating at the idea of replacing as much human input as possible with a machine best known for generating titles of books that don&amp;#8217;t&amp;nbsp;exist.&lt;/p&gt;
&lt;p&gt;I don&amp;#8217;t know how you get &amp;#8220;better&amp;#8221; than this.  I don&amp;#8217;t know how you make a &lt;em&gt;better&lt;/em&gt; Whatever&amp;nbsp;machine.&lt;/p&gt;
&lt;h3 id="and-then-theres-the-art-thing"&gt;&lt;a class="toclink" href="#and-then-theres-the-art-thing"&gt;And then there's the art thing&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;I glimpsed someone on Twitter a few days ago, also scoffing at the idea that anyone would decide &lt;em&gt;not&lt;/em&gt; to use the Whatever machine.  I can&amp;#8217;t remember exactly what they said, but it was something like: &amp;#8220;I created a whole album, complete with album art, in 3.5 hours.  Why wouldn&amp;#8217;t I use the make it easier&amp;nbsp;machine?&amp;#8221;&lt;/p&gt;
&lt;p&gt;This is kind of darkly fascinating to me, because it gives rise to such an obvious question: if &lt;em&gt;anyone&lt;/em&gt; can do that, then &lt;em&gt;why listen to your music&lt;/em&gt;?  It takes a significant chunk of 3.5 hours just to &lt;em&gt;listen&lt;/em&gt; to an album, so how much manual work was even done here?  Apparently I can just go generate an endless stream of stuff of the same quality!  Why would I want your particular brand of&amp;nbsp;Whatever?&lt;/p&gt;
&lt;p&gt;Nobody seems to appreciate that if you can make a computer do something entirely on its own, then that becomes the &lt;em&gt;baseline&lt;/em&gt;.  &lt;/p&gt;
&lt;p&gt;There is a lot that can be said about image generation (little of it polite), but I&amp;#8217;m running out of steam a little here.  I&amp;#8217;d intended to comment on the ongoing efforts to make better and better &lt;em&gt;photo-quality&lt;/em&gt; image generation, but I can&amp;#8217;t think of much to say beyond: &lt;strong&gt;why the fuck would you work on that?&lt;/strong&gt;  We don&amp;#8217;t have enough trouble with, say, the conservative &amp;#8220;news&amp;#8221; sphere inventing its own alternate reality that millions of people buy into, simply by &lt;em&gt;lying&lt;/em&gt; — now we have to give them a machine tailor-made for creating fake photos and videos too?  Why does this need to exist?  Why is this &lt;em&gt;in my phone&amp;#8217;s fucking camera app&lt;/em&gt;?  Can&amp;#8217;t these people go live on an airgapped island somewhere and work on their new horrifying fraud machine by&amp;nbsp;themselves?&lt;/p&gt;
&lt;h3 id="also-i-could-swear-i-saw-google-advertise-that-gemini-can-do-your-homework-for-you"&gt;&lt;a class="toclink" href="#also-i-could-swear-i-saw-google-advertise-that-gemini-can-do-your-homework-for-you"&gt;Also I could swear I saw Google advertise that Gemini can do your homework for you&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;This is starting to get away from the main thesis of Whatever but every time I hear about students coasting through school just using LLMs, I wonder what we are doing to humanity&amp;#8217;s ability to think critically about anything.  It already wasn&amp;#8217;t &lt;em&gt;great&lt;/em&gt;, but now we&amp;#8217;re raising a whole generation on a machine that gives them Whatever, and they just take it.  You&amp;#8217;ve seen anecdotes of people posting comments and submitting papers and whatnot with obvious tells like &amp;#8220;As a large language model&amp;#8230;&amp;#8221; in them.  That means they aren&amp;#8217;t even reading the words they claim as their own!  They just produce&amp;nbsp;Whatever.&lt;/p&gt;
&lt;p&gt;Actually hang on this gets me into conclusion&amp;nbsp;territory.&lt;/p&gt;
&lt;h2 id="enough-of-whatever"&gt;&lt;a class="toclink" href="#enough-of-whatever"&gt;Enough of Whatever&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;I remember that Facebook literally proposed running a bunch of its own &lt;span class="caps"&gt;LLM&lt;/span&gt;-driven fake accounts on its own website.  Fake people making fake posts about Whatever, so you&amp;#8217;ll have more Whatever to look at, so you&amp;#8217;ll see more ads along the way.  Monetize the rot, I&amp;nbsp;guess.&lt;/p&gt;
&lt;p&gt;I can&amp;#8217;t imagine publishing a game with, say, Midjourney-generated art, even if it &lt;em&gt;didn&amp;#8217;t&lt;/em&gt; have uncanny otherworldly surfaces bleeding into each other.  I would find that &lt;em&gt;humiliating&lt;/em&gt;.  But there are games on the Switch shop that do it.&amp;nbsp;Whatever.&lt;/p&gt;
&lt;p&gt;It begins to feel like a broad celebration of mediocrity.  &lt;em&gt;Finally&lt;/em&gt;, society says, with a huge sigh of relief.  &lt;em&gt;I don&amp;#8217;t have to write a letter to my granddaughter.  I don&amp;#8217;t have to write a three-line fetch call.  I don&amp;#8217;t have to know anything, care about what I&amp;#8217;m doing, or even have an&amp;nbsp;opinion.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;I can just substitute some Content™.  I can just ask the computer for&amp;nbsp;Whatever&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;But I &lt;em&gt;like&lt;/em&gt; programming.  I &lt;em&gt;like&lt;/em&gt; writing.  I like &lt;em&gt;making things&lt;/em&gt; and then being able to sit back and look at them and think, holy fuck, &lt;em&gt;I made that&lt;/em&gt;.  There is no joy for me in typing a vague description into a computer and refreshing my way through a parade of Whatever until something is good&amp;nbsp;enough.&lt;/p&gt;
&lt;p&gt;The most obnoxious people like to talk about how Stable Diffusion is &amp;#8220;democratizing art&amp;#8221; and that is the dumbest thing I&amp;#8217;ve ever heard.  There is no fucking King of Art decreeing who is allowed to draw and who isn&amp;#8217;t.  You could do it.  You could do it right now.  But it&amp;#8217;s hard, so you&amp;#8217;d rather spend that time crying on Twitter about how unfair it is that &lt;em&gt;learning a skill takes work&lt;/em&gt; and thank god the computer can give you all of the admiration with none of the effort&amp;nbsp;now.&lt;/p&gt;
&lt;p&gt;This is an incredibly weird moment.  There have always been inventions that make some craft easier (but sometimes a little more shoddy as well).  There have always been people who resented the idea that the thing they work very hard at is now more accessible.  America&amp;#8217;s Protestant work culture is deeply entangled with this as well, but I don&amp;#8217;t value sweat in and of itself — I have a broader&amp;nbsp;objection.&lt;/p&gt;
&lt;p&gt;Because this is something else.  What&amp;#8217;s being sold to us is a machine that is promised to do &lt;em&gt;everything&lt;/em&gt;.  That&amp;#8217;s far beyond a tiny question like &amp;#8220;should you know how to manually focus in order to take a photograph&amp;#8221; — it gets at the notion of &lt;em&gt;thinking about, or doing,&lt;/em&gt; &lt;strong&gt;&lt;em&gt;anything at all&lt;/em&gt;&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;I don&amp;#8217;t think anyone is obligated to do anything in particular.  If you don&amp;#8217;t want to draw, or write, or compose, or program, or whatever, then don&amp;#8217;t!  That&amp;#8217;s&amp;nbsp;fine.&lt;/p&gt;
&lt;p&gt;But I think the core of what pisses me off is that selling this magic machine &lt;strong&gt;requires&lt;/strong&gt; selling the idea that &lt;em&gt;doing things is worthless&lt;/em&gt;.  Because if &lt;em&gt;doing something&lt;/em&gt; has some value, then it must be somehow &lt;em&gt;better&lt;/em&gt; than pushing a button and receiving Whatever for essentially no cost.  If you&amp;#8217;re some assclown like Sam Altman, whose graph-go-up depends on convincing you to replace all your employees with ChatGPT, you &lt;em&gt;have to destroy that idea&lt;/em&gt;.  It is the greatest threat to your business model.  You have to destroy the idea that &lt;em&gt;things are worth doing&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;I think that sucks, I think he sucks, and I think his machine sucks.  So fuck him and fuck his&amp;nbsp;machine.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Do things.  Make things.&lt;/strong&gt;  And then put them on your website so I can see&amp;nbsp;them.&lt;/p&gt;</content><category term="articles"></category></entry><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 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 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&amp;nbsp;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&amp;nbsp;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 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="articles"></category><category term="games"></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&amp;nbsp;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 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&amp;nbsp;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&amp;nbsp;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&amp;nbsp;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&amp;nbsp;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 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&amp;nbsp;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&amp;nbsp;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&amp;nbsp;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&amp;nbsp;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;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="linenos"&gt;1&lt;/span&gt;autoloadlights true
&lt;span class="linenos"&gt;2&lt;/span&gt;autoloadbrightmaps true
&lt;/code&gt;&lt;/pre&gt;&lt;/div&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&amp;nbsp;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 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?&amp;nbsp;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;,&amp;nbsp;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&amp;nbsp;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="articles"></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 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 structure&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;As of where I left off last time, my&amp;nbsp;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;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="linenos"&gt; 1&lt;/span&gt;&lt;span class="kr"&gt;function&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;nop&lt;/span&gt;&lt;span class="p"&gt;(...)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kr"&gt;return&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="kr"&gt;end&lt;/span&gt;
&lt;span class="linenos"&gt; 2&lt;/span&gt;
&lt;span class="linenos"&gt; 3&lt;/span&gt;&lt;span class="c1"&gt;--------------------------------&lt;/span&gt;
&lt;span class="linenos"&gt; 4&lt;/span&gt;&lt;span class="c1"&gt;-- simple object type&lt;/span&gt;
&lt;span class="linenos"&gt; 5&lt;/span&gt;&lt;span class="kd"&gt;local&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;obj&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="p"&gt;{&lt;/span&gt;&lt;span class="nv"&gt;init&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="nv"&gt;nop&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="linenos"&gt; 6&lt;/span&gt;&lt;span class="nv"&gt;obj&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="py"&gt;__index&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="nv"&gt;obj&lt;/span&gt;
&lt;span class="linenos"&gt; 7&lt;/span&gt;
&lt;span class="linenos"&gt; 8&lt;/span&gt;&lt;span class="kr"&gt;function&lt;/span&gt;&lt;span class="w"&gt; &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="linenos"&gt; 9&lt;/span&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="kd"&gt;local&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;o&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;setmetatable&lt;/span&gt;&lt;span class="p"&gt;({},&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;self&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="linenos"&gt;10&lt;/span&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="kr"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;o&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;o&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="linenos"&gt;11&lt;/span&gt;&lt;span class="kr"&gt;end&lt;/span&gt;
&lt;span class="linenos"&gt;12&lt;/span&gt;
&lt;span class="linenos"&gt;13&lt;/span&gt;&lt;span class="c1"&gt;-- subclassing&lt;/span&gt;
&lt;span class="linenos"&gt;14&lt;/span&gt;&lt;span class="kr"&gt;function&lt;/span&gt;&lt;span class="w"&gt; &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="nv"&gt;proto&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="linenos"&gt;15&lt;/span&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nv"&gt;proto&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="nv"&gt;proto&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ow"&gt;or&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{}&lt;/span&gt;
&lt;span class="linenos"&gt;16&lt;/span&gt;
&lt;span class="linenos"&gt;17&lt;/span&gt;&lt;span class="w"&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="linenos"&gt;18&lt;/span&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="kr"&gt;for&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;k&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;v&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kr"&gt;in&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;pairs&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;self&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kr"&gt;do&lt;/span&gt;
&lt;span class="linenos"&gt;19&lt;/span&gt;&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="kr"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;sub&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;k&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="mi"&gt;2&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="s2"&gt;&amp;quot;__&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kr"&gt;then&lt;/span&gt;
&lt;span class="linenos"&gt;20&lt;/span&gt;&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="nv"&gt;proto&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;k&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="nv"&gt;v&lt;/span&gt;
&lt;span class="linenos"&gt;21&lt;/span&gt;&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="kr"&gt;end&lt;/span&gt;
&lt;span class="linenos"&gt;22&lt;/span&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="kr"&gt;end&lt;/span&gt;
&lt;span class="linenos"&gt;23&lt;/span&gt;
&lt;span class="linenos"&gt;24&lt;/span&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nv"&gt;proto&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="py"&gt;__index&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="nv"&gt;proto&lt;/span&gt;
&lt;span class="linenos"&gt;25&lt;/span&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nv"&gt;proto&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="py"&gt;__super&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="nv"&gt;self&lt;/span&gt;
&lt;span class="linenos"&gt;26&lt;/span&gt;
&lt;span class="linenos"&gt;27&lt;/span&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="kr"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;setmetatable&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;proto&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;self&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="linenos"&gt;28&lt;/span&gt;&lt;span class="kr"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&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&amp;nbsp;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;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="linenos"&gt; 1&lt;/span&gt;&lt;span class="kd"&gt;local&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;vec&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="nv"&gt;obj&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="py"&gt;extend&lt;/span&gt;&lt;span class="p"&gt;{}&lt;/span&gt;
&lt;span class="linenos"&gt; 2&lt;/span&gt;
&lt;span class="linenos"&gt; 3&lt;/span&gt;&lt;span class="kr"&gt;function&lt;/span&gt;&lt;span class="w"&gt; &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="nv"&gt;x&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;y&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="linenos"&gt; 4&lt;/span&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nv"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="py"&gt;x&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="nv"&gt;x&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ow"&gt;or&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;
&lt;span class="linenos"&gt; 5&lt;/span&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nv"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="py"&gt;y&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="nv"&gt;y&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ow"&gt;or&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;
&lt;span class="linenos"&gt; 6&lt;/span&gt;&lt;span class="kr"&gt;end&lt;/span&gt;
&lt;span class="linenos"&gt; 7&lt;/span&gt;
&lt;span class="linenos"&gt; 8&lt;/span&gt;&lt;span class="kr"&gt;function&lt;/span&gt;&lt;span class="w"&gt; &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="nv"&gt;v&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="linenos"&gt; 9&lt;/span&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="kr"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;vec&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="py"&gt;x&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="nv"&gt;v&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="py"&gt;x&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="py"&gt;y&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="nv"&gt;v&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="py"&gt;y&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="linenos"&gt;10&lt;/span&gt;&lt;span class="kr"&gt;end&lt;/span&gt;
&lt;span class="linenos"&gt;11&lt;/span&gt;
&lt;span class="linenos"&gt;12&lt;/span&gt;&lt;span class="kr"&gt;function&lt;/span&gt;&lt;span class="w"&gt; &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="nv"&gt;v&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="linenos"&gt;13&lt;/span&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="kr"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;vec&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="py"&gt;x&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="nv"&gt;v&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="py"&gt;x&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="py"&gt;y&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="nv"&gt;v&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="py"&gt;y&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="linenos"&gt;14&lt;/span&gt;&lt;span class="kr"&gt;end&lt;/span&gt;
&lt;span class="linenos"&gt;15&lt;/span&gt;
&lt;span class="linenos"&gt;16&lt;/span&gt;&lt;span class="kr"&gt;function&lt;/span&gt;&lt;span class="w"&gt; &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="nv"&gt;v&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="linenos"&gt;17&lt;/span&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nv"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="py"&gt;x&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="nv"&gt;v&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="py"&gt;x&lt;/span&gt;
&lt;span class="linenos"&gt;18&lt;/span&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nv"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="py"&gt;y&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="nv"&gt;v&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="py"&gt;y&lt;/span&gt;
&lt;span class="linenos"&gt;19&lt;/span&gt;&lt;span class="kr"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&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&amp;nbsp;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 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 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&amp;nbsp;in &lt;code&gt;vec:init&lt;/code&gt;, loosely called a &lt;em&gt;constructor&lt;/em&gt;, which stores them&amp;nbsp;in &lt;code&gt;self.x&lt;/code&gt; and &lt;code&gt;self.y&lt;/code&gt; —&amp;nbsp;where &lt;code&gt;self&lt;/code&gt; is&amp;nbsp;the &lt;code&gt;vec&lt;/code&gt; being&amp;nbsp;created.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="linenos"&gt;1&lt;/span&gt;&lt;span class="c1"&gt;-- this is example code, not part of the game&lt;/span&gt;
&lt;span class="linenos"&gt;2&lt;/span&gt;&lt;span class="kd"&gt;local&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&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="nf"&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="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="linenos"&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="s2"&gt;&amp;quot;x = &amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;a&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="py"&gt;x&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; y = &amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;a&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="py"&gt;y&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&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;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;&amp;nbsp;a &lt;code&gt;vec&lt;/code&gt;.  It&amp;#8217;s like&amp;nbsp;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&amp;nbsp;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&amp;nbsp;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;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="linenos"&gt;1&lt;/span&gt;&lt;span class="c1"&gt;-- example code&lt;/span&gt;
&lt;span class="linenos"&gt;2&lt;/span&gt;&lt;span class="kd"&gt;local&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&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="nf"&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="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="linenos"&gt;3&lt;/span&gt;&lt;span class="kd"&gt;local&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;w&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;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="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="linenos"&gt;4&lt;/span&gt;&lt;span class="nv"&gt;v&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="nv"&gt;w&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="linenos"&gt;5&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="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;v&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="py"&gt;x&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; y = &amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;v&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="py"&gt;y&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&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;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 it.&lt;/p&gt;
&lt;/aside&gt;
&lt;p&gt;Finally, those&amp;nbsp;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&amp;nbsp;the &lt;code&gt;obj&lt;/code&gt; gobbledygook does) — they let me&amp;nbsp;use &lt;code&gt;+&lt;/code&gt; and &lt;code&gt;-&lt;/code&gt; on&amp;nbsp;my &lt;code&gt;vec&lt;/code&gt;s just like they were&amp;nbsp;numbers.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="linenos"&gt;1&lt;/span&gt;&lt;span class="c1"&gt;-- example code&lt;/span&gt;
&lt;span class="linenos"&gt;2&lt;/span&gt;&lt;span class="kd"&gt;local&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;q&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;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="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="linenos"&gt;3&lt;/span&gt;&lt;span class="kd"&gt;local&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;r&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;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="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="linenos"&gt;4&lt;/span&gt;&lt;span class="kd"&gt;local&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;s&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="nv"&gt;q&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="nv"&gt;r&lt;/span&gt;
&lt;span class="linenos"&gt;5&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="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;s&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="py"&gt;x&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; y = &amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;s&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="py"&gt;y&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&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;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.&amp;nbsp;A &lt;code&gt;vec&lt;/code&gt; has some data&amp;nbsp;— &lt;code&gt;x&lt;/code&gt; and &lt;code&gt;y&lt;/code&gt; — and some code — for adding&amp;nbsp;another &lt;code&gt;vec&lt;/code&gt; to itself.  If I later discover some new thing I want&amp;nbsp;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&amp;nbsp;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 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;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="linenos"&gt; 1&lt;/span&gt;&lt;span class="kr"&gt;function&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;b2n&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;b&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="linenos"&gt; 2&lt;/span&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="kr"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;b&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ow"&gt;and&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="ow"&gt;or&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;
&lt;span class="linenos"&gt; 3&lt;/span&gt;&lt;span class="kr"&gt;end&lt;/span&gt;
&lt;span class="linenos"&gt; 4&lt;/span&gt;
&lt;span class="linenos"&gt; 5&lt;/span&gt;&lt;span class="kd"&gt;local&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;t&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="linenos"&gt; 6&lt;/span&gt;&lt;span class="kd"&gt;local&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;player&lt;/span&gt;
&lt;span class="linenos"&gt; 7&lt;/span&gt;
&lt;span class="linenos"&gt; 8&lt;/span&gt;&lt;span class="kd"&gt;local&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;anise_stand&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="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="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;17&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;18&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;33&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;34&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="linenos"&gt; 9&lt;/span&gt;&lt;span class="kd"&gt;local&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;anise_jump&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="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="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;17&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;18&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;19&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;35&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="linenos"&gt;10&lt;/span&gt;&lt;span class="kd"&gt;local&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;anise&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="nv"&gt;obj&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="py"&gt;extend&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="linenos"&gt;11&lt;/span&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nv"&gt;move&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;vec&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
&lt;span class="linenos"&gt;12&lt;/span&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nv"&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="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="linenos"&gt;13&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="linenos"&gt;14&lt;/span&gt;
&lt;span class="linenos"&gt;15&lt;/span&gt;&lt;span class="kr"&gt;function&lt;/span&gt;&lt;span class="w"&gt; &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="nv"&gt;pos&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="linenos"&gt;16&lt;/span&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nv"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="py"&gt;pos&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="nv"&gt;pos&lt;/span&gt;
&lt;span class="linenos"&gt;17&lt;/span&gt;&lt;span class="kr"&gt;end&lt;/span&gt;
&lt;span class="linenos"&gt;18&lt;/span&gt;
&lt;span class="linenos"&gt;19&lt;/span&gt;&lt;span class="kr"&gt;function&lt;/span&gt;&lt;span class="w"&gt; &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="linenos"&gt;20&lt;/span&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="kr"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="py"&gt;move&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="py"&gt;x&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="mi"&gt;0&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kr"&gt;then&lt;/span&gt;
&lt;span class="linenos"&gt;21&lt;/span&gt;&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nv"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="py"&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="kc"&gt;false&lt;/span&gt;
&lt;span class="linenos"&gt;22&lt;/span&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="kr"&gt;elseif&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="py"&gt;move&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="py"&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="mi"&gt;0&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kr"&gt;then&lt;/span&gt;
&lt;span class="linenos"&gt;23&lt;/span&gt;&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nv"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="py"&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="kc"&gt;true&lt;/span&gt;
&lt;span class="linenos"&gt;24&lt;/span&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="kr"&gt;end&lt;/span&gt;
&lt;span class="linenos"&gt;25&lt;/span&gt;
&lt;span class="linenos"&gt;26&lt;/span&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nv"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="py"&gt;pos&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="nv"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="py"&gt;move&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="linenos"&gt;27&lt;/span&gt;&lt;span class="kr"&gt;end&lt;/span&gt;
&lt;span class="linenos"&gt;28&lt;/span&gt;
&lt;span class="linenos"&gt;29&lt;/span&gt;&lt;span class="kr"&gt;function&lt;/span&gt;&lt;span class="w"&gt; &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="linenos"&gt;30&lt;/span&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="kd"&gt;local&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;pose&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="nv"&gt;anise_stand&lt;/span&gt;
&lt;span class="linenos"&gt;31&lt;/span&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="kr"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="py"&gt;move&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="py"&gt;x&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="w"&gt; &lt;/span&gt;&lt;span class="ow"&gt;or&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="py"&gt;move&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="py"&gt;y&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="ow"&gt;and&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;t&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="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="mi"&gt;4&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kr"&gt;then&lt;/span&gt;
&lt;span class="linenos"&gt;32&lt;/span&gt;&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nv"&gt;pose&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="nv"&gt;anise_jump&lt;/span&gt;
&lt;span class="linenos"&gt;33&lt;/span&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="kr"&gt;end&lt;/span&gt;
&lt;span class="linenos"&gt;34&lt;/span&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="kd"&gt;local&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;y&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="nv"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="py"&gt;pos&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="py"&gt;y&lt;/span&gt;
&lt;span class="linenos"&gt;35&lt;/span&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="kd"&gt;local&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;x0&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="nv"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="py"&gt;pos&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="py"&gt;x&lt;/span&gt;
&lt;span class="linenos"&gt;36&lt;/span&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="kd"&gt;local&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;dx&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="linenos"&gt;37&lt;/span&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="kr"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="py"&gt;left&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kr"&gt;then&lt;/span&gt;
&lt;span class="linenos"&gt;38&lt;/span&gt;&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nv"&gt;dx&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="mi"&gt;8&lt;/span&gt;
&lt;span class="linenos"&gt;39&lt;/span&gt;&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nv"&gt;x0&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="linenos"&gt;40&lt;/span&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="kr"&gt;end&lt;/span&gt;
&lt;span class="linenos"&gt;41&lt;/span&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="kd"&gt;local&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;x&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="nv"&gt;x0&lt;/span&gt;
&lt;span class="linenos"&gt;42&lt;/span&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="kr"&gt;for&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&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="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;#&lt;/span&gt;&lt;span class="nv"&gt;pose&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kr"&gt;do&lt;/span&gt;
&lt;span class="linenos"&gt;43&lt;/span&gt;&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nf"&gt;spr&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;pose&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;i&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;x&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;y&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="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="nv"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="py"&gt;left&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="linenos"&gt;44&lt;/span&gt;&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="kr"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&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;2&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="w"&gt; &lt;/span&gt;&lt;span class="kr"&gt;then&lt;/span&gt;
&lt;span class="linenos"&gt;45&lt;/span&gt;&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="nv"&gt;x&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="nv"&gt;x0&lt;/span&gt;
&lt;span class="linenos"&gt;46&lt;/span&gt;&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="nv"&gt;y&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="linenos"&gt;47&lt;/span&gt;&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="kr"&gt;else&lt;/span&gt;
&lt;span class="linenos"&gt;48&lt;/span&gt;&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="nv"&gt;x&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="nv"&gt;dx&lt;/span&gt;
&lt;span class="linenos"&gt;49&lt;/span&gt;&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="kr"&gt;end&lt;/span&gt;
&lt;span class="linenos"&gt;50&lt;/span&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="kr"&gt;end&lt;/span&gt;
&lt;span class="linenos"&gt;51&lt;/span&gt;&lt;span class="kr"&gt;end&lt;/span&gt;
&lt;span class="linenos"&gt;52&lt;/span&gt;
&lt;span class="linenos"&gt;53&lt;/span&gt;&lt;span class="kr"&gt;function&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;_init&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="linenos"&gt;54&lt;/span&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nv"&gt;player&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;anise&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&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="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;64&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;span class="linenos"&gt;55&lt;/span&gt;&lt;span class="kr"&gt;end&lt;/span&gt;
&lt;span class="linenos"&gt;56&lt;/span&gt;
&lt;span class="linenos"&gt;57&lt;/span&gt;&lt;span class="kr"&gt;function&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;_update&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="linenos"&gt;58&lt;/span&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nv"&gt;t&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="linenos"&gt;59&lt;/span&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nv"&gt;t&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;120&lt;/span&gt;
&lt;span class="linenos"&gt;60&lt;/span&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nv"&gt;player&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="py"&gt;move&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;vec&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
&lt;span class="linenos"&gt;61&lt;/span&gt;&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nf"&gt;b2n&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&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="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;b2n&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&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="linenos"&gt;62&lt;/span&gt;&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nf"&gt;b2n&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&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="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;b2n&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&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="linenos"&gt;63&lt;/span&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nv"&gt;player&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="linenos"&gt;64&lt;/span&gt;&lt;span class="kr"&gt;end&lt;/span&gt;
&lt;span class="linenos"&gt;65&lt;/span&gt;
&lt;span class="linenos"&gt;66&lt;/span&gt;&lt;span class="kr"&gt;function&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;_draw&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="linenos"&gt;67&lt;/span&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nf"&gt;cls&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="linenos"&gt;68&lt;/span&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nv"&gt;player&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="linenos"&gt;69&lt;/span&gt;&lt;span class="kr"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&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&amp;nbsp;new &lt;code&gt;anise:draw()&lt;/code&gt; method has basically been cut and pasted from my&amp;nbsp;old &lt;code&gt;_draw()&lt;/code&gt; — all except&amp;nbsp;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&amp;nbsp;the &lt;code&gt;px&lt;/code&gt; and &lt;code&gt;py&lt;/code&gt; variables into a single&amp;nbsp;vector, &lt;code&gt;pos&lt;/code&gt; (short for &amp;#8220;position&amp;#8221;), which I now have to refer to&amp;nbsp;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&amp;nbsp;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 tokens.&lt;/p&gt;
&lt;/aside&gt;
&lt;p&gt;A Star Anise object is created and assigned&amp;nbsp;to &lt;code&gt;player&lt;/code&gt; when the game starts, and&amp;nbsp;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&amp;nbsp;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&amp;nbsp;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&amp;nbsp;in &lt;code&gt;_update()&lt;/code&gt; using this&amp;nbsp;mouthful:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="linenos"&gt;1&lt;/span&gt;&lt;span class="c1"&gt;-- top-level&lt;/span&gt;
&lt;span class="linenos"&gt;2&lt;/span&gt;&lt;span class="kr"&gt;function&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;b2n&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;b&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="linenos"&gt;3&lt;/span&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="kr"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;b&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ow"&gt;and&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="ow"&gt;or&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;
&lt;span class="linenos"&gt;4&lt;/span&gt;&lt;span class="kr"&gt;end&lt;/span&gt;
&lt;span class="linenos"&gt;5&lt;/span&gt;
&lt;span class="linenos"&gt;6&lt;/span&gt;&lt;span class="c1"&gt;-- in _update()&lt;/span&gt;
&lt;span class="linenos"&gt;7&lt;/span&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nv"&gt;player&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="py"&gt;move&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;vec&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
&lt;span class="linenos"&gt;8&lt;/span&gt;&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nf"&gt;b2n&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&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="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;b2n&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&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="linenos"&gt;9&lt;/span&gt;&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nf"&gt;b2n&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&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="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;b2n&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&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;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&amp;nbsp;turns &lt;code&gt;true&lt;/code&gt; into 1&amp;nbsp;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 writing:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="linenos"&gt;1&lt;/span&gt;&lt;span class="kr"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;a&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kr"&gt;then&lt;/span&gt;
&lt;span class="linenos"&gt;2&lt;/span&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nv"&gt;x&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="nv"&gt;b&lt;/span&gt;
&lt;span class="linenos"&gt;3&lt;/span&gt;&lt;span class="kr"&gt;else&lt;/span&gt;
&lt;span class="linenos"&gt;4&lt;/span&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nv"&gt;x&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="nv"&gt;c&lt;/span&gt;
&lt;span class="linenos"&gt;5&lt;/span&gt;&lt;span class="kr"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&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 possible.&lt;/p&gt;
&lt;/aside&gt;
&lt;p&gt;Unpacking that a bit&amp;nbsp;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 movement!&lt;/p&gt;
&lt;/aside&gt;
&lt;p&gt;Once Star&amp;nbsp;Anise&amp;#8217;s &lt;code&gt;move&lt;/code&gt; is set, the rest works similarly to before: I&amp;nbsp;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&amp;nbsp;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 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;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="linenos"&gt; 1&lt;/span&gt;&lt;span class="kd"&gt;local&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;actor&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="nv"&gt;obj&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="py"&gt;extend&lt;/span&gt;&lt;span class="p"&gt;{}&lt;/span&gt;
&lt;span class="linenos"&gt; 2&lt;/span&gt;
&lt;span class="linenos"&gt; 3&lt;/span&gt;&lt;span class="kr"&gt;function&lt;/span&gt;&lt;span class="w"&gt; &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="nv"&gt;pos&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="linenos"&gt; 4&lt;/span&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nv"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="py"&gt;pos&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="nv"&gt;pos&lt;/span&gt;
&lt;span class="linenos"&gt; 5&lt;/span&gt;&lt;span class="kr"&gt;end&lt;/span&gt;
&lt;span class="linenos"&gt; 6&lt;/span&gt;
&lt;span class="linenos"&gt; 7&lt;/span&gt;&lt;span class="kr"&gt;function&lt;/span&gt;&lt;span class="w"&gt; &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="linenos"&gt; 8&lt;/span&gt;&lt;span class="kr"&gt;end&lt;/span&gt;
&lt;span class="linenos"&gt; 9&lt;/span&gt;
&lt;span class="linenos"&gt;10&lt;/span&gt;&lt;span class="kr"&gt;function&lt;/span&gt;&lt;span class="w"&gt; &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="linenos"&gt;11&lt;/span&gt;&lt;span class="kr"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&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&amp;nbsp;have &lt;code&gt;anise&lt;/code&gt; &lt;em&gt;inherit&lt;/em&gt;&amp;nbsp;from &lt;code&gt;actor&lt;/code&gt;.  That means he&amp;#8217;ll become a specialized kind&amp;nbsp;of &lt;code&gt;actor&lt;/code&gt;, and in particular, all the methods&amp;nbsp;on &lt;code&gt;actor&lt;/code&gt; will also appear&amp;nbsp;on &lt;code&gt;anise&lt;/code&gt;.  You may notice&amp;nbsp;that &lt;code&gt;anise&lt;/code&gt; was previously a specialized kind&amp;nbsp;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&amp;nbsp;call &lt;code&gt;vec(x, y)&lt;/code&gt; like a function is that it inherits some magic stuff&amp;nbsp;from &lt;code&gt;obj&lt;/code&gt;.&amp;nbsp;Surprise!&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="linenos"&gt;1&lt;/span&gt;&lt;span class="kd"&gt;local&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;anise&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="nv"&gt;actor&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="py"&gt;extend&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;I can now&amp;nbsp;delete &lt;code&gt;anise:init()&lt;/code&gt;, since it&amp;#8217;s identical&amp;nbsp;to &lt;code&gt;actor:init()&lt;/code&gt;.  I still&amp;nbsp;have &lt;code&gt;anise:update()&lt;/code&gt; and &lt;code&gt;anise:draw()&lt;/code&gt;, which override the methods&amp;nbsp;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&amp;nbsp;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 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 things.&lt;/p&gt;
&lt;/aside&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="linenos"&gt; 1&lt;/span&gt;&lt;span class="c1"&gt;-- at the top&lt;/span&gt;
&lt;span class="linenos"&gt; 2&lt;/span&gt;&lt;span class="kd"&gt;local&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;actors&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="p"&gt;{}&lt;/span&gt;
&lt;span class="linenos"&gt; 3&lt;/span&gt;
&lt;span class="linenos"&gt; 4&lt;/span&gt;&lt;span class="kr"&gt;function&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;_init&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="linenos"&gt; 5&lt;/span&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nv"&gt;player&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;anise&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&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="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;64&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;span class="linenos"&gt; 6&lt;/span&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nf"&gt;add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;actors&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;player&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="linenos"&gt; 7&lt;/span&gt;&lt;span class="kr"&gt;end&lt;/span&gt;
&lt;span class="linenos"&gt; 8&lt;/span&gt;
&lt;span class="linenos"&gt; 9&lt;/span&gt;&lt;span class="kr"&gt;function&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;_update&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="linenos"&gt;10&lt;/span&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="c1"&gt;-- ...mostly same as before...&lt;/span&gt;
&lt;span class="linenos"&gt;11&lt;/span&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="kr"&gt;for&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;actor&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kr"&gt;in&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;all&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;actors&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kr"&gt;do&lt;/span&gt;
&lt;span class="linenos"&gt;12&lt;/span&gt;&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nv"&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="linenos"&gt;13&lt;/span&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="kr"&gt;end&lt;/span&gt;
&lt;span class="linenos"&gt;14&lt;/span&gt;&lt;span class="kr"&gt;end&lt;/span&gt;
&lt;span class="linenos"&gt;15&lt;/span&gt;
&lt;span class="linenos"&gt;16&lt;/span&gt;&lt;span class="kr"&gt;function&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;_draw&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="linenos"&gt;17&lt;/span&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nf"&gt;cls&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="linenos"&gt;18&lt;/span&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="kr"&gt;for&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;actor&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kr"&gt;in&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;all&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;actors&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kr"&gt;do&lt;/span&gt;
&lt;span class="linenos"&gt;19&lt;/span&gt;&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nv"&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="linenos"&gt;20&lt;/span&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="kr"&gt;end&lt;/span&gt;
&lt;span class="linenos"&gt;21&lt;/span&gt;&lt;span class="kr"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;This does pretty much what it reads like.&amp;nbsp;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.&amp;nbsp;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&amp;nbsp;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&amp;nbsp;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 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 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 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;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="linenos"&gt;1&lt;/span&gt;&lt;span class="kr"&gt;function&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;_draw&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="linenos"&gt;2&lt;/span&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nf"&gt;cls&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="linenos"&gt;3&lt;/span&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nf"&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="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="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="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="mi"&gt;32&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;32&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="linenos"&gt;4&lt;/span&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="kr"&gt;for&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;actor&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kr"&gt;in&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;all&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;actors&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kr"&gt;do&lt;/span&gt;
&lt;span class="linenos"&gt;5&lt;/span&gt;&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nv"&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="linenos"&gt;6&lt;/span&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="kr"&gt;end&lt;/span&gt;
&lt;span class="linenos"&gt;7&lt;/span&gt;&lt;span class="kr"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&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 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 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&amp;nbsp;of &lt;code&gt;anise:update()&lt;/code&gt;.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="linenos"&gt;1&lt;/span&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nv"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="py"&gt;pos&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="nv"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="py"&gt;move&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&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;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="linenos"&gt;1&lt;/span&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nv"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="py"&gt;pos&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="nv"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="py"&gt;move&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="linenos"&gt;2&lt;/span&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="kr"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="py"&gt;pos&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="py"&gt;y&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="mi"&gt;88&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kr"&gt;then&lt;/span&gt;
&lt;span class="linenos"&gt;3&lt;/span&gt;&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nv"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="py"&gt;pos&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="py"&gt;y&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;88&lt;/span&gt;
&lt;span class="linenos"&gt;4&lt;/span&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="kr"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&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 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&amp;nbsp;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&amp;nbsp;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;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="linenos"&gt;1&lt;/span&gt;&lt;span class="kd"&gt;local&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;lunekos&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="p"&gt;{&lt;/span&gt;
&lt;span class="linenos"&gt;2&lt;/span&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nv"&gt;anise&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="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="linenos"&gt;3&lt;/span&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nv"&gt;purrl&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="s2"&gt;&amp;quot;purrl is very lovely&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="linenos"&gt;4&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="linenos"&gt;5&lt;/span&gt;&lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&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;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;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="linenos"&gt;1&lt;/span&gt;&lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;lunekos&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="py"&gt;anise&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 as above&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&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&amp;nbsp;the &lt;code&gt;__call&lt;/code&gt; metamethod.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="linenos"&gt; 1&lt;/span&gt;&lt;span class="kd"&gt;local&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;t&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="p"&gt;{&lt;/span&gt;
&lt;span class="linenos"&gt; 2&lt;/span&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nv"&gt;stuff&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;5678&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="linenos"&gt; 3&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="linenos"&gt; 4&lt;/span&gt;&lt;span class="kd"&gt;local&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;meta&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="p"&gt;{&lt;/span&gt;
&lt;span class="linenos"&gt; 5&lt;/span&gt;&lt;span class="w"&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="linenos"&gt; 6&lt;/span&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nv"&gt;__call&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="kr"&gt;function&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;tbl&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="linenos"&gt; 7&lt;/span&gt;&lt;span class="w"&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="w"&gt; &lt;/span&gt;&lt;span class="nv"&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="linenos"&gt; 8&lt;/span&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="kr"&gt;end&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="linenos"&gt; 9&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="linenos"&gt;10&lt;/span&gt;&lt;span class="nb"&gt;setmetatable&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;t&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;meta&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="linenos"&gt;11&lt;/span&gt;&lt;span class="nf"&gt;t&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="c1"&gt;-- my stuff is 5678&lt;/span&gt;
&lt;span class="linenos"&gt;12&lt;/span&gt;&lt;span class="nv"&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="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;yoinky&amp;quot;&lt;/span&gt;
&lt;span class="linenos"&gt;13&lt;/span&gt;&lt;span class="nf"&gt;t&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&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;p&gt;One especially useful metamethod&amp;nbsp;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;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="linenos"&gt; 1&lt;/span&gt;&lt;span class="kd"&gt;local&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;counts&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="p"&gt;{&lt;/span&gt;
&lt;span class="linenos"&gt; 2&lt;/span&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nv"&gt;apples&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;5&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="linenos"&gt; 3&lt;/span&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nv"&gt;bananas&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;3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="linenos"&gt; 4&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="linenos"&gt; 5&lt;/span&gt;&lt;span class="nb"&gt;setmetatable&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;counts&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="linenos"&gt; 6&lt;/span&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nv"&gt;__index&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="kr"&gt;function&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;tbl&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;key&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="linenos"&gt; 7&lt;/span&gt;&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="kr"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;
&lt;span class="linenos"&gt; 8&lt;/span&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="kr"&gt;end&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="linenos"&gt; 9&lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt;
&lt;span class="linenos"&gt;10&lt;/span&gt;&lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;counts&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="py"&gt;bananas&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="c1"&gt;-- 3&lt;/span&gt;
&lt;span class="linenos"&gt;11&lt;/span&gt;&lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;counts&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="py"&gt;mangoes&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="c1"&gt;-- 0&lt;/span&gt;
&lt;span class="linenos"&gt;12&lt;/span&gt;&lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;counts&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="py"&gt;apples&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="c1"&gt;-- 5&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Instead of a&amp;nbsp;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&amp;nbsp;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&amp;nbsp;the &lt;code&gt;obj&lt;/code&gt; snippet, so here it is&amp;nbsp;again.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="linenos"&gt; 1&lt;/span&gt;&lt;span class="kr"&gt;function&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;nop&lt;/span&gt;&lt;span class="p"&gt;(...)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kr"&gt;return&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="kr"&gt;end&lt;/span&gt;
&lt;span class="linenos"&gt; 2&lt;/span&gt;
&lt;span class="linenos"&gt; 3&lt;/span&gt;&lt;span class="kd"&gt;local&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;obj&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="p"&gt;{&lt;/span&gt;&lt;span class="nv"&gt;init&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="nv"&gt;nop&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="linenos"&gt; 4&lt;/span&gt;&lt;span class="nv"&gt;obj&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="py"&gt;__index&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="nv"&gt;obj&lt;/span&gt;
&lt;span class="linenos"&gt; 5&lt;/span&gt;
&lt;span class="linenos"&gt; 6&lt;/span&gt;&lt;span class="kr"&gt;function&lt;/span&gt;&lt;span class="w"&gt; &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="linenos"&gt; 7&lt;/span&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="kd"&gt;local&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;o&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;setmetatable&lt;/span&gt;&lt;span class="p"&gt;({},&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;self&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="linenos"&gt; 8&lt;/span&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="kr"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;o&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;o&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="linenos"&gt; 9&lt;/span&gt;&lt;span class="kr"&gt;end&lt;/span&gt;
&lt;span class="linenos"&gt;10&lt;/span&gt;
&lt;span class="linenos"&gt;11&lt;/span&gt;&lt;span class="c1"&gt;-- subclassing&lt;/span&gt;
&lt;span class="linenos"&gt;12&lt;/span&gt;&lt;span class="kr"&gt;function&lt;/span&gt;&lt;span class="w"&gt; &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="nv"&gt;proto&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="linenos"&gt;13&lt;/span&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nv"&gt;proto&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="nv"&gt;proto&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ow"&gt;or&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{}&lt;/span&gt;
&lt;span class="linenos"&gt;14&lt;/span&gt;
&lt;span class="linenos"&gt;15&lt;/span&gt;&lt;span class="w"&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="linenos"&gt;16&lt;/span&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="kr"&gt;for&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;k&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;v&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kr"&gt;in&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;pairs&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;self&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kr"&gt;do&lt;/span&gt;
&lt;span class="linenos"&gt;17&lt;/span&gt;&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="kr"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;sub&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;k&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="mi"&gt;2&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="s2"&gt;&amp;quot;__&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kr"&gt;then&lt;/span&gt;
&lt;span class="linenos"&gt;18&lt;/span&gt;&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="nv"&gt;proto&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;k&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="nv"&gt;v&lt;/span&gt;
&lt;span class="linenos"&gt;19&lt;/span&gt;&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="kr"&gt;end&lt;/span&gt;
&lt;span class="linenos"&gt;20&lt;/span&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="kr"&gt;end&lt;/span&gt;
&lt;span class="linenos"&gt;21&lt;/span&gt;
&lt;span class="linenos"&gt;22&lt;/span&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nv"&gt;proto&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="py"&gt;__index&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="nv"&gt;proto&lt;/span&gt;
&lt;span class="linenos"&gt;23&lt;/span&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nv"&gt;proto&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="py"&gt;__super&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="nv"&gt;self&lt;/span&gt;
&lt;span class="linenos"&gt;24&lt;/span&gt;
&lt;span class="linenos"&gt;25&lt;/span&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="kr"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;setmetatable&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;proto&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;self&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="linenos"&gt;26&lt;/span&gt;&lt;span class="kr"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&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&amp;nbsp;own &lt;code&gt;__index&lt;/code&gt;.  At first, we have&amp;nbsp;only &lt;code&gt;obj&lt;/code&gt;, which looks like&amp;nbsp;this:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="linenos"&gt;1&lt;/span&gt;&lt;span class="kd"&gt;local&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;obj&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="p"&gt;{&lt;/span&gt;
&lt;span class="linenos"&gt;2&lt;/span&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nv"&gt;init&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="nv"&gt;nop&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="linenos"&gt;3&lt;/span&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nv"&gt;__index&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="nv"&gt;obj&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="linenos"&gt;4&lt;/span&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nv"&gt;__call&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="kr"&gt;function&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="kr"&gt;end&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="linenos"&gt;5&lt;/span&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nv"&gt;extend&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="kr"&gt;function&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="kr"&gt;end&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="linenos"&gt;6&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Now we&amp;nbsp;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&amp;nbsp;like &lt;code&gt;__call&lt;/code&gt; directly in the metatable and&amp;nbsp;ignores &lt;code&gt;__index&lt;/code&gt;, so I copy them into the new prototype.  Then I make the prototype its&amp;nbsp;own &lt;code&gt;__index&lt;/code&gt;, as&amp;nbsp;with &lt;code&gt;obj&lt;/code&gt;, and also remember the &amp;#8220;superclass&amp;#8221;&amp;nbsp;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.&amp;nbsp;So &lt;code&gt;foo{}&lt;/code&gt; just&amp;nbsp;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;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="linenos"&gt;1&lt;/span&gt;&lt;span class="kd"&gt;local&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;vec&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="p"&gt;{&lt;/span&gt;
&lt;span class="linenos"&gt;2&lt;/span&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nv"&gt;__index&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="nv"&gt;vec&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="linenos"&gt;3&lt;/span&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nv"&gt;__super&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="nv"&gt;obj&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="linenos"&gt;4&lt;/span&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nv"&gt;__call&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="nv"&gt;obj&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="py"&gt;__call&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="linenos"&gt;5&lt;/span&gt;
&lt;span class="linenos"&gt;6&lt;/span&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nv"&gt;METATABLE&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="nv"&gt;obj&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="linenos"&gt;7&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Remember this&amp;nbsp;syntax?&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="linenos"&gt;1&lt;/span&gt;&lt;span class="kr"&gt;function&lt;/span&gt;&lt;span class="w"&gt; &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="nv"&gt;x&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;y&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="linenos"&gt;2&lt;/span&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nv"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="py"&gt;x&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="nv"&gt;x&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ow"&gt;or&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;
&lt;span class="linenos"&gt;3&lt;/span&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nv"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="py"&gt;y&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="nv"&gt;y&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ow"&gt;or&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;
&lt;span class="linenos"&gt;4&lt;/span&gt;&lt;span class="kr"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;That is exactly equivalent&amp;nbsp;to:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="linenos"&gt;1&lt;/span&gt;&lt;span class="nv"&gt;vec&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="py"&gt;init&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="kr"&gt;function&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;x&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;y&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="linenos"&gt;2&lt;/span&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nv"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="py"&gt;x&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="nv"&gt;x&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ow"&gt;or&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;
&lt;span class="linenos"&gt;3&lt;/span&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nv"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="py"&gt;y&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="nv"&gt;y&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ow"&gt;or&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;
&lt;span class="linenos"&gt;4&lt;/span&gt;&lt;span class="kr"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;So after all is said and done, we&amp;nbsp;have:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="linenos"&gt; 1&lt;/span&gt;&lt;span class="kd"&gt;local&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;vec&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="p"&gt;{&lt;/span&gt;
&lt;span class="linenos"&gt; 2&lt;/span&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nv"&gt;__index&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="nv"&gt;vec&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="linenos"&gt; 3&lt;/span&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nv"&gt;__super&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="nv"&gt;obj&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="linenos"&gt; 4&lt;/span&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nv"&gt;__call&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="nv"&gt;obj&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="py"&gt;__call&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="linenos"&gt; 5&lt;/span&gt;
&lt;span class="linenos"&gt; 6&lt;/span&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nv"&gt;init&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="kr"&gt;function&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="kr"&gt;end&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="linenos"&gt; 7&lt;/span&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nv"&gt;__add&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="kr"&gt;function&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="kr"&gt;end&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="linenos"&gt; 8&lt;/span&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nv"&gt;__sub&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="kr"&gt;function&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="kr"&gt;end&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="linenos"&gt; 9&lt;/span&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nv"&gt;iadd&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="kr"&gt;function&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="kr"&gt;end&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="linenos"&gt;10&lt;/span&gt;
&lt;span class="linenos"&gt;11&lt;/span&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nv"&gt;METATABLE&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="nv"&gt;obj&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="linenos"&gt;12&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Now for the magic part.  When I&amp;nbsp;call &lt;code&gt;vec()&lt;/code&gt;, Lua checks the metatable.&amp;nbsp;(The &lt;code&gt;__call&lt;/code&gt; in the main table does nothing!)  The metatable&amp;nbsp;is &lt;code&gt;obj&lt;/code&gt;, which does have&amp;nbsp;a &lt;code&gt;__call&lt;/code&gt;, so Lua calls that function and&amp;nbsp;inserts &lt;code&gt;vec&lt;/code&gt; as the first argument.&amp;nbsp;Then &lt;code&gt;obj.__call&lt;/code&gt; creates an empty table,&amp;nbsp;assigns &lt;code&gt;self&lt;/code&gt; (which is the first argument,&amp;nbsp;so &lt;code&gt;vec&lt;/code&gt;) as the empty table&amp;#8217;s metatable, and calls the new&amp;nbsp;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;&amp;nbsp;an &lt;code&gt;init&lt;/code&gt; method.  No problem: it has a metatable with&amp;nbsp;an &lt;code&gt;__index&lt;/code&gt;, so Lua consults that instead.  The&amp;nbsp;metatable&amp;#8217;s &lt;code&gt;__index&lt;/code&gt; is &lt;code&gt;vec&lt;/code&gt;,&amp;nbsp;and &lt;code&gt;vec&lt;/code&gt; &lt;em&gt;does&lt;/em&gt; contain&amp;nbsp;an &lt;code&gt;init&lt;/code&gt;, so that&amp;#8217;s what gets called.  (If there were&amp;nbsp;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&amp;nbsp;an &lt;code&gt;__index&lt;/code&gt;, and continued along.  That&amp;#8217;s why I didn&amp;#8217;t need&amp;nbsp;an &lt;code&gt;anise.init&lt;/code&gt;.)&lt;/p&gt;
&lt;p&gt;That&amp;#8217;s also why&amp;nbsp;defining &lt;code&gt;vec:__add&lt;/code&gt; works — it puts&amp;nbsp;the &lt;code&gt;__add&lt;/code&gt; metamethod&amp;nbsp;into &lt;code&gt;vec&lt;/code&gt;, which becomes the metatable for all vector objects, thus automatically&amp;nbsp;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&amp;nbsp;call &lt;code&gt;obj&lt;/code&gt; itself.  Pop quiz: why&amp;nbsp;not?&lt;/p&gt;</content><category term="articles"></category><category term="tech"></category><category term="gamedev"></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/blog/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/blog/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 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="https://eev.ee/blog/2020/05/10/star-anise-chronicles-oh-no-wheres-twig/"&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 upon.&lt;/p&gt;
&lt;p&gt;(Actually, Twitter stopped using eggs as default avatars in April 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 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 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 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 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 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 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 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, 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 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&amp;nbsp;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 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&amp;nbsp;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&amp;nbsp;like &lt;code&gt;133&lt;/code&gt;, a name&amp;nbsp;like &lt;code&gt;animframedelay&lt;/code&gt;, an operator&amp;nbsp;like &lt;code&gt;+&lt;/code&gt;, a keyword&amp;nbsp;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&amp;nbsp;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&amp;nbsp;(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 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 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 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&amp;nbsp;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 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&amp;nbsp;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 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 for.&lt;/p&gt;
&lt;p&gt;Boy, I sure am using these things a lot already.&lt;/p&gt;
&lt;/aside&gt;
&lt;h3 id="sound-editor"&gt;&lt;a class="toclink" href="#sound-editor"&gt;Sound 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&amp;nbsp;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/blog/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 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 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;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="linenos"&gt; 1&lt;/span&gt;&lt;span class="kr"&gt;function&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;_init&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="linenos"&gt; 2&lt;/span&gt;&lt;span class="kr"&gt;end&lt;/span&gt;
&lt;span class="linenos"&gt; 3&lt;/span&gt;
&lt;span class="linenos"&gt; 4&lt;/span&gt;&lt;span class="kr"&gt;function&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;_update&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="linenos"&gt; 5&lt;/span&gt;&lt;span class="kr"&gt;end&lt;/span&gt;
&lt;span class="linenos"&gt; 6&lt;/span&gt;
&lt;span class="linenos"&gt; 7&lt;/span&gt;&lt;span class="kr"&gt;function&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;_draw&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="linenos"&gt; 8&lt;/span&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nf"&gt;cls&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="linenos"&gt; 9&lt;/span&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nf"&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="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;64&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;64&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="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="linenos"&gt;10&lt;/span&gt;&lt;span class="kr"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&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&amp;nbsp;starts, &lt;code&gt;_update&lt;/code&gt; runs every frame,&amp;nbsp;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 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 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&amp;nbsp;calls &lt;code&gt;_update&lt;/code&gt; to advance the game, then it&amp;nbsp;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&amp;nbsp;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&amp;nbsp;call &lt;code&gt;_update&lt;/code&gt; &lt;em&gt;twice&lt;/em&gt; and then&amp;nbsp;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;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="linenos"&gt;1&lt;/span&gt;        | tic                   | tic                   |
&lt;span class="linenos"&gt;2&lt;/span&gt;--------+-----------------------+-----------------------+
&lt;span class="linenos"&gt;3&lt;/span&gt;30 FPS: | _update() _draw()     | _update() _draw()     |
&lt;span class="linenos"&gt;4&lt;/span&gt;--------+-----------------------+-----------------------+
&lt;span class="linenos"&gt;5&lt;/span&gt;15 FPS: | _update() _update() _draw()                   |
&lt;/code&gt;&lt;/pre&gt;&lt;/div&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:&amp;nbsp;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&amp;nbsp;in &lt;code&gt;_draw&lt;/code&gt; is clear the screen, which is accomplished by the charmingly&amp;nbsp;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&amp;nbsp;called &lt;code&gt;spr()&lt;/code&gt; to draw Star Anise.  The usual arguments&amp;nbsp;are &lt;code&gt;spr(n, x, y)&lt;/code&gt;,&amp;nbsp;where &lt;code&gt;n&lt;/code&gt; is the sprite number (visible near the middle of the screen in the sprite editor)&amp;nbsp;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&amp;nbsp;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&amp;nbsp;with &lt;code&gt;btn(b)&lt;/code&gt;,&amp;nbsp;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;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="linenos"&gt; 1&lt;/span&gt;&lt;span class="kr"&gt;function&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;_init&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="linenos"&gt; 2&lt;/span&gt;&lt;span class="kr"&gt;end&lt;/span&gt;
&lt;span class="linenos"&gt; 3&lt;/span&gt;
&lt;span class="linenos"&gt; 4&lt;/span&gt;&lt;span class="kd"&gt;local&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;px&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;64&lt;/span&gt;
&lt;span class="linenos"&gt; 5&lt;/span&gt;&lt;span class="kd"&gt;local&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;py&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;64&lt;/span&gt;
&lt;span class="linenos"&gt; 6&lt;/span&gt;
&lt;span class="linenos"&gt; 7&lt;/span&gt;&lt;span class="kr"&gt;function&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;_update&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="linenos"&gt; 8&lt;/span&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="kr"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&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="w"&gt; &lt;/span&gt;&lt;span class="kr"&gt;then&lt;/span&gt;
&lt;span class="linenos"&gt; 9&lt;/span&gt;&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nv"&gt;py&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="linenos"&gt;10&lt;/span&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="kr"&gt;end&lt;/span&gt;
&lt;span class="linenos"&gt;11&lt;/span&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="kr"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&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="w"&gt; &lt;/span&gt;&lt;span class="kr"&gt;then&lt;/span&gt;
&lt;span class="linenos"&gt;12&lt;/span&gt;&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nv"&gt;py&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="linenos"&gt;13&lt;/span&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="kr"&gt;end&lt;/span&gt;
&lt;span class="linenos"&gt;14&lt;/span&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="kr"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&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="w"&gt; &lt;/span&gt;&lt;span class="kr"&gt;then&lt;/span&gt;
&lt;span class="linenos"&gt;15&lt;/span&gt;&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nv"&gt;px&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="linenos"&gt;16&lt;/span&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="kr"&gt;end&lt;/span&gt;
&lt;span class="linenos"&gt;17&lt;/span&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="kr"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&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="w"&gt; &lt;/span&gt;&lt;span class="kr"&gt;then&lt;/span&gt;
&lt;span class="linenos"&gt;18&lt;/span&gt;&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nv"&gt;px&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="linenos"&gt;19&lt;/span&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="kr"&gt;end&lt;/span&gt;
&lt;span class="linenos"&gt;20&lt;/span&gt;&lt;span class="kr"&gt;end&lt;/span&gt;
&lt;span class="linenos"&gt;21&lt;/span&gt;
&lt;span class="linenos"&gt;22&lt;/span&gt;&lt;span class="kr"&gt;function&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;_draw&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="linenos"&gt;23&lt;/span&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nf"&gt;cls&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="linenos"&gt;24&lt;/span&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nf"&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="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;px&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;py&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="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="linenos"&gt;25&lt;/span&gt;&lt;span class="kr"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Here I&amp;#8217;ve put his position (still anchored at his top-left) into some variables, and&amp;nbsp;during &lt;code&gt;_update()&lt;/code&gt; I update them.  (If you&amp;#8217;re familiar with Lua, you may balk&amp;nbsp;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 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 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 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 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 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 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 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 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;&amp;nbsp;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;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="linenos"&gt; 1&lt;/span&gt;&lt;span class="kr"&gt;function&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;_init&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="linenos"&gt; 2&lt;/span&gt;&lt;span class="kr"&gt;end&lt;/span&gt;
&lt;span class="linenos"&gt; 3&lt;/span&gt;
&lt;span class="linenos"&gt; 4&lt;/span&gt;&lt;span class="kd"&gt;local&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;px&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;64&lt;/span&gt;
&lt;span class="linenos"&gt; 5&lt;/span&gt;&lt;span class="kd"&gt;local&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;py&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;64&lt;/span&gt;
&lt;span class="linenos"&gt; 6&lt;/span&gt;&lt;span class="kd"&gt;local&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&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="kc"&gt;false&lt;/span&gt;
&lt;span class="linenos"&gt; 7&lt;/span&gt;
&lt;span class="linenos"&gt; 8&lt;/span&gt;&lt;span class="kr"&gt;function&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;_update&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="linenos"&gt; 9&lt;/span&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="kr"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&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="w"&gt; &lt;/span&gt;&lt;span class="kr"&gt;then&lt;/span&gt;
&lt;span class="linenos"&gt;10&lt;/span&gt;&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nv"&gt;py&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="linenos"&gt;11&lt;/span&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="kr"&gt;end&lt;/span&gt;
&lt;span class="linenos"&gt;12&lt;/span&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="kr"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&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="w"&gt; &lt;/span&gt;&lt;span class="kr"&gt;then&lt;/span&gt;
&lt;span class="linenos"&gt;13&lt;/span&gt;&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nv"&gt;py&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="linenos"&gt;14&lt;/span&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="kr"&gt;end&lt;/span&gt;
&lt;span class="linenos"&gt;15&lt;/span&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="kr"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&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="w"&gt; &lt;/span&gt;&lt;span class="kr"&gt;then&lt;/span&gt;
&lt;span class="linenos"&gt;16&lt;/span&gt;&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nv"&gt;px&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="linenos"&gt;17&lt;/span&gt;&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nv"&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="kc"&gt;true&lt;/span&gt;
&lt;span class="linenos"&gt;18&lt;/span&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="kr"&gt;end&lt;/span&gt;
&lt;span class="linenos"&gt;19&lt;/span&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="kr"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&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="w"&gt; &lt;/span&gt;&lt;span class="kr"&gt;then&lt;/span&gt;
&lt;span class="linenos"&gt;20&lt;/span&gt;&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nv"&gt;px&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="linenos"&gt;21&lt;/span&gt;&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nv"&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="kc"&gt;false&lt;/span&gt;
&lt;span class="linenos"&gt;22&lt;/span&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="kr"&gt;end&lt;/span&gt;
&lt;span class="linenos"&gt;23&lt;/span&gt;&lt;span class="kr"&gt;end&lt;/span&gt;
&lt;span class="linenos"&gt;24&lt;/span&gt;
&lt;span class="linenos"&gt;25&lt;/span&gt;&lt;span class="kr"&gt;function&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;_draw&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="linenos"&gt;26&lt;/span&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nf"&gt;cls&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="linenos"&gt;27&lt;/span&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nf"&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="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;px&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;py&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="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;left&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="linenos"&gt;28&lt;/span&gt;&lt;span class="kr"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&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&amp;nbsp;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;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="linenos"&gt; 1&lt;/span&gt;&lt;span class="kd"&gt;local&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;anise_stand&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="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="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;17&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;18&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;33&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;34&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="linenos"&gt; 2&lt;/span&gt;&lt;span class="kd"&gt;local&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;anise_jump&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="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="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;17&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;18&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;19&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;35&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="linenos"&gt; 3&lt;/span&gt;
&lt;span class="linenos"&gt; 4&lt;/span&gt;&lt;span class="kr"&gt;function&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;_init&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="linenos"&gt; 5&lt;/span&gt;&lt;span class="kr"&gt;end&lt;/span&gt;
&lt;span class="linenos"&gt; 6&lt;/span&gt;
&lt;span class="linenos"&gt; 7&lt;/span&gt;&lt;span class="kd"&gt;local&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;t&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="linenos"&gt; 8&lt;/span&gt;&lt;span class="kd"&gt;local&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;px&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;64&lt;/span&gt;
&lt;span class="linenos"&gt; 9&lt;/span&gt;&lt;span class="kd"&gt;local&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;py&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;64&lt;/span&gt;
&lt;span class="linenos"&gt;10&lt;/span&gt;&lt;span class="kd"&gt;local&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&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="kc"&gt;false&lt;/span&gt;
&lt;span class="linenos"&gt;11&lt;/span&gt;&lt;span class="kd"&gt;local&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;moving&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="kc"&gt;false&lt;/span&gt;
&lt;span class="linenos"&gt;12&lt;/span&gt;
&lt;span class="linenos"&gt;13&lt;/span&gt;&lt;span class="kr"&gt;function&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;_update&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="linenos"&gt;14&lt;/span&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nv"&gt;t&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="linenos"&gt;15&lt;/span&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nv"&gt;t&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;120&lt;/span&gt;
&lt;span class="linenos"&gt;16&lt;/span&gt;
&lt;span class="linenos"&gt;17&lt;/span&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nv"&gt;moving&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="kc"&gt;false&lt;/span&gt;
&lt;span class="linenos"&gt;18&lt;/span&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="kr"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&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="w"&gt; &lt;/span&gt;&lt;span class="kr"&gt;then&lt;/span&gt;
&lt;span class="linenos"&gt;19&lt;/span&gt;&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nv"&gt;py&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="linenos"&gt;20&lt;/span&gt;&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nv"&gt;moving&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="kc"&gt;true&lt;/span&gt;
&lt;span class="linenos"&gt;21&lt;/span&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="kr"&gt;end&lt;/span&gt;
&lt;span class="linenos"&gt;22&lt;/span&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="kr"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&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="w"&gt; &lt;/span&gt;&lt;span class="kr"&gt;then&lt;/span&gt;
&lt;span class="linenos"&gt;23&lt;/span&gt;&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nv"&gt;py&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="linenos"&gt;24&lt;/span&gt;&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nv"&gt;moving&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="kc"&gt;true&lt;/span&gt;
&lt;span class="linenos"&gt;25&lt;/span&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="kr"&gt;end&lt;/span&gt;
&lt;span class="linenos"&gt;26&lt;/span&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="kr"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&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="w"&gt; &lt;/span&gt;&lt;span class="kr"&gt;then&lt;/span&gt;
&lt;span class="linenos"&gt;27&lt;/span&gt;&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nv"&gt;px&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="linenos"&gt;28&lt;/span&gt;&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nv"&gt;moving&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="kc"&gt;true&lt;/span&gt;
&lt;span class="linenos"&gt;29&lt;/span&gt;&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nv"&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="kc"&gt;true&lt;/span&gt;
&lt;span class="linenos"&gt;30&lt;/span&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="kr"&gt;end&lt;/span&gt;
&lt;span class="linenos"&gt;31&lt;/span&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="kr"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&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="w"&gt; &lt;/span&gt;&lt;span class="kr"&gt;then&lt;/span&gt;
&lt;span class="linenos"&gt;32&lt;/span&gt;&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nv"&gt;px&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="linenos"&gt;33&lt;/span&gt;&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nv"&gt;moving&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="kc"&gt;true&lt;/span&gt;
&lt;span class="linenos"&gt;34&lt;/span&gt;&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nv"&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="kc"&gt;false&lt;/span&gt;
&lt;span class="linenos"&gt;35&lt;/span&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="kr"&gt;end&lt;/span&gt;
&lt;span class="linenos"&gt;36&lt;/span&gt;&lt;span class="kr"&gt;end&lt;/span&gt;
&lt;span class="linenos"&gt;37&lt;/span&gt;
&lt;span class="linenos"&gt;38&lt;/span&gt;&lt;span class="kr"&gt;function&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;_draw&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="linenos"&gt;39&lt;/span&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nf"&gt;cls&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="linenos"&gt;40&lt;/span&gt;
&lt;span class="linenos"&gt;41&lt;/span&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="kd"&gt;local&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;pose&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="nv"&gt;anise_stand&lt;/span&gt;
&lt;span class="linenos"&gt;42&lt;/span&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="kr"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;moving&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ow"&gt;and&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;t&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="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="mi"&gt;4&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kr"&gt;then&lt;/span&gt;
&lt;span class="linenos"&gt;43&lt;/span&gt;&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nv"&gt;pose&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="nv"&gt;anise_jump&lt;/span&gt;
&lt;span class="linenos"&gt;44&lt;/span&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="kr"&gt;end&lt;/span&gt;
&lt;span class="linenos"&gt;45&lt;/span&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="kd"&gt;local&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;y&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="nv"&gt;py&lt;/span&gt;
&lt;span class="linenos"&gt;46&lt;/span&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="kd"&gt;local&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;x0&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="nv"&gt;px&lt;/span&gt;
&lt;span class="linenos"&gt;47&lt;/span&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="kd"&gt;local&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;dx&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="linenos"&gt;48&lt;/span&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="kr"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;left&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kr"&gt;then&lt;/span&gt;
&lt;span class="linenos"&gt;49&lt;/span&gt;&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nv"&gt;dx&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="mi"&gt;8&lt;/span&gt;
&lt;span class="linenos"&gt;50&lt;/span&gt;&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nv"&gt;x0&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="linenos"&gt;51&lt;/span&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="kr"&gt;end&lt;/span&gt;
&lt;span class="linenos"&gt;52&lt;/span&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="kd"&gt;local&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;x&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="nv"&gt;x0&lt;/span&gt;
&lt;span class="linenos"&gt;53&lt;/span&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="kr"&gt;for&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&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="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;#&lt;/span&gt;&lt;span class="nv"&gt;pose&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kr"&gt;do&lt;/span&gt;
&lt;span class="linenos"&gt;54&lt;/span&gt;&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nf"&gt;spr&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;pose&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;i&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;x&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;y&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="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="nv"&gt;left&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="linenos"&gt;55&lt;/span&gt;&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="kr"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&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;2&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="w"&gt; &lt;/span&gt;&lt;span class="kr"&gt;then&lt;/span&gt;
&lt;span class="linenos"&gt;56&lt;/span&gt;&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="nv"&gt;x&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="nv"&gt;x0&lt;/span&gt;
&lt;span class="linenos"&gt;57&lt;/span&gt;&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="nv"&gt;y&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="linenos"&gt;58&lt;/span&gt;&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="kr"&gt;else&lt;/span&gt;
&lt;span class="linenos"&gt;59&lt;/span&gt;&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="nv"&gt;x&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="nv"&gt;dx&lt;/span&gt;
&lt;span class="linenos"&gt;60&lt;/span&gt;&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="kr"&gt;end&lt;/span&gt;
&lt;span class="linenos"&gt;61&lt;/span&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="kr"&gt;end&lt;/span&gt;
&lt;span class="linenos"&gt;62&lt;/span&gt;&lt;span class="kr"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&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&amp;nbsp;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,&amp;nbsp;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&amp;nbsp;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 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&amp;nbsp;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;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="linenos"&gt;1&lt;/span&gt;&lt;span class="c1"&gt;-- top of the file&lt;/span&gt;
&lt;span class="linenos"&gt;2&lt;/span&gt;&lt;span class="kd"&gt;local&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;anise_stand&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="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="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;17&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;18&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;33&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;34&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="linenos"&gt;3&lt;/span&gt;&lt;span class="kd"&gt;local&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;anise_jump&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="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="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;17&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;18&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;19&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;35&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="linenos"&gt;4&lt;/span&gt;
&lt;span class="linenos"&gt;5&lt;/span&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="c1"&gt;-- in _draw()&lt;/span&gt;
&lt;span class="linenos"&gt;6&lt;/span&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="kd"&gt;local&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;pose&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="nv"&gt;anise_stand&lt;/span&gt;
&lt;span class="linenos"&gt;7&lt;/span&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="kr"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;moving&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ow"&gt;and&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;t&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="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="mi"&gt;4&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kr"&gt;then&lt;/span&gt;
&lt;span class="linenos"&gt;8&lt;/span&gt;&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nv"&gt;pose&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="nv"&gt;anise_jump&lt;/span&gt;
&lt;span class="linenos"&gt;9&lt;/span&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="kr"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&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&amp;nbsp;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&amp;nbsp;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;&amp;nbsp;if &lt;code&gt;t % 8 &amp;lt; 4&lt;/code&gt;.&amp;nbsp;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;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="linenos"&gt;45&lt;/span&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="kd"&gt;local&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;y&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="nv"&gt;py&lt;/span&gt;
&lt;span class="linenos"&gt;46&lt;/span&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="kd"&gt;local&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;x0&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="nv"&gt;px&lt;/span&gt;
&lt;span class="linenos"&gt;47&lt;/span&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="kd"&gt;local&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;dx&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="linenos"&gt;48&lt;/span&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="kr"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;left&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kr"&gt;then&lt;/span&gt;
&lt;span class="linenos"&gt;49&lt;/span&gt;&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nv"&gt;dx&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="mi"&gt;8&lt;/span&gt;
&lt;span class="linenos"&gt;50&lt;/span&gt;&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nv"&gt;x0&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="linenos"&gt;51&lt;/span&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="kr"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&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&amp;nbsp;(&lt;span class="math"&gt;\(x_0\)&lt;/span&gt;) and a change is written with the Greek letter delta&amp;nbsp;(&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&amp;nbsp;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;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="linenos"&gt;52&lt;/span&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="kd"&gt;local&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;x&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="nv"&gt;x0&lt;/span&gt;
&lt;span class="linenos"&gt;53&lt;/span&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="kr"&gt;for&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&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="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;#&lt;/span&gt;&lt;span class="nv"&gt;pose&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kr"&gt;do&lt;/span&gt;
&lt;span class="linenos"&gt;54&lt;/span&gt;&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nf"&gt;spr&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;pose&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;i&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;x&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;y&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="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="nv"&gt;left&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="linenos"&gt;55&lt;/span&gt;&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="kr"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&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;2&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="w"&gt; &lt;/span&gt;&lt;span class="kr"&gt;then&lt;/span&gt;
&lt;span class="linenos"&gt;56&lt;/span&gt;&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="nv"&gt;x&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="nv"&gt;x0&lt;/span&gt;
&lt;span class="linenos"&gt;57&lt;/span&gt;&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="nv"&gt;y&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="linenos"&gt;58&lt;/span&gt;&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="kr"&gt;else&lt;/span&gt;
&lt;span class="linenos"&gt;59&lt;/span&gt;&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="nv"&gt;x&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="nv"&gt;dx&lt;/span&gt;
&lt;span class="linenos"&gt;60&lt;/span&gt;&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="kr"&gt;end&lt;/span&gt;
&lt;span class="linenos"&gt;61&lt;/span&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="kr"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;All that&amp;#8217;s left to do is the drawing itself.  For each tile in&amp;nbsp;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;&amp;nbsp;(&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&amp;nbsp;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 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&amp;nbsp;(= &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&amp;nbsp;supported: &lt;code&gt;a += b&lt;/code&gt; works as&amp;nbsp;in &lt;code&gt;a = a + b&lt;/code&gt; in stock Lua,&amp;nbsp;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&amp;nbsp;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&amp;nbsp;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&amp;nbsp;new &lt;code&gt;@&lt;/code&gt;, &lt;code&gt;%&lt;/code&gt;,&amp;nbsp;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&amp;nbsp;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&amp;nbsp;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;,&amp;nbsp;and &lt;code&gt;xpcall&lt;/code&gt; are not available (though the lack&amp;nbsp;of &lt;code&gt;select&lt;/code&gt; might be a&amp;nbsp;bug).&lt;/p&gt;
&lt;p&gt;The built-in&amp;nbsp;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&amp;nbsp;with &lt;code&gt;tonum&lt;/code&gt; and &lt;code&gt;tostr&lt;/code&gt;, which behave slightly differently&amp;nbsp;(but &lt;code&gt;tostr&lt;/code&gt; does still respect&amp;nbsp;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;,&amp;nbsp;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&amp;nbsp;as &lt;code&gt;cocreate&lt;/code&gt;, &lt;code&gt;coresume&lt;/code&gt;, &lt;code&gt;costatus&lt;/code&gt;,&amp;nbsp;and &lt;code&gt;yield&lt;/code&gt;.  There is no equivalent&amp;nbsp;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&amp;nbsp;and &lt;code&gt;package&lt;/code&gt; library are not available, though&amp;nbsp;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&amp;nbsp;are: &lt;code&gt;chr&lt;/code&gt;, &lt;code&gt;ord&lt;/code&gt;, &lt;code&gt;split&lt;/code&gt;,&amp;nbsp;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&amp;nbsp;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&amp;nbsp;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&amp;nbsp;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&amp;nbsp;— &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&amp;nbsp;— &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;&amp;nbsp;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="articles"></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="articles"></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&amp;nbsp;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 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&amp;nbsp;a &lt;code&gt;border-top&lt;/code&gt;.  That&amp;#8217;s&amp;nbsp;an &lt;code&gt;&amp;lt;hr&amp;gt;&lt;/code&gt;.  The page title is almost certainly centered with,&amp;nbsp;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&amp;nbsp;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;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="linenos"&gt;1&lt;/span&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;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&amp;nbsp;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;,&amp;nbsp;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;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="linenos"&gt; 1&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;span class="linenos"&gt; 2&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="linenos"&gt; 3&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="linenos"&gt; 4&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="linenos"&gt; 5&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="linenos"&gt; 6&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="linenos"&gt; 7&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="linenos"&gt; 8&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="linenos"&gt; 9&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="linenos"&gt;10&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="linenos"&gt;11&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="linenos"&gt;12&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="linenos"&gt;13&lt;/span&gt;                (actual page content goes here)
&lt;span class="linenos"&gt;14&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="linenos"&gt;15&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="linenos"&gt;16&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="linenos"&gt;17&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;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 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&amp;nbsp;in &lt;code&gt;.htm&lt;/code&gt; rather&amp;nbsp;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;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="linenos"&gt;1&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="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;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;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="linenos"&gt; 1&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;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="linenos"&gt; 2&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="linenos"&gt; 3&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="linenos"&gt; 4&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="linenos"&gt; 5&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="linenos"&gt; 6&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="linenos"&gt; 7&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="linenos"&gt; 8&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="linenos"&gt; 9&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="linenos"&gt;10&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="linenos"&gt;11&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="linenos"&gt;12&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="linenos"&gt;13&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="linenos"&gt;14&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="linenos"&gt;15&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="linenos"&gt;16&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="linenos"&gt;17&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="linenos"&gt;18&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="linenos"&gt;19&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="linenos"&gt;20&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="linenos"&gt;21&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="linenos"&gt;22&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="linenos"&gt;23&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="linenos"&gt;24&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="linenos"&gt;25&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="linenos"&gt;26&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="linenos"&gt;27&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="linenos"&gt;28&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="linenos"&gt;29&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="linenos"&gt;30&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="linenos"&gt;31&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="linenos"&gt;32&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="linenos"&gt;33&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="linenos"&gt;34&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="linenos"&gt;35&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="linenos"&gt;36&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="linenos"&gt;37&lt;/span&gt;...
&lt;span class="linenos"&gt;38&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;p&gt;That&amp;#8217;s the first two rows, including the logo.  You get the idea.  Everything is laid out&amp;nbsp;with &lt;code&gt;align&lt;/code&gt; and &lt;code&gt;valign&lt;/code&gt; on table&amp;nbsp;cells; &lt;code&gt;rowspan&lt;/code&gt;s&amp;nbsp;and &lt;code&gt;colspan&lt;/code&gt;s are used frequently; and there are&amp;nbsp;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;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="linenos"&gt;1&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;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="linenos"&gt;2&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="linenos"&gt;3&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="linenos"&gt;4&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="linenos"&gt;5&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="linenos"&gt;6&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="linenos"&gt;7&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;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&amp;nbsp;the &lt;code&gt;&amp;lt;body&amp;gt;&lt;/code&gt; has&amp;nbsp;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;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="linenos"&gt;1&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/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="linenos"&gt;2&lt;/span&gt;
&lt;span class="linenos"&gt;3&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="linenos"&gt;4&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="linenos"&gt;5&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="linenos"&gt;6&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="linenos"&gt;7&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="linenos"&gt;8&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;p&gt;I assume this is more or less self-explanatory.&amp;nbsp;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 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;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="linenos"&gt;1&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="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;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;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="linenos"&gt;1&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;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="linenos"&gt;2&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="linenos"&gt;3&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="linenos"&gt;4&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="linenos"&gt;5&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;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&amp;nbsp;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 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;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="linenos"&gt;1&lt;/span&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="linenos"&gt;2&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="linenos"&gt;3&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Bam!  You&amp;#8217;re done.  No matter how&amp;nbsp;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&amp;nbsp;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;&amp;nbsp;like &lt;code&gt;P.important&lt;/code&gt; would only&amp;nbsp;affect &lt;code&gt;&amp;lt;P CLASS="important"&amp;gt;&lt;/code&gt;,&amp;nbsp;and &lt;code&gt;#header&lt;/code&gt; would only&amp;nbsp;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&amp;nbsp;the &lt;code&gt;float&lt;/code&gt; property, the equivalent&amp;nbsp;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&amp;nbsp;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&amp;nbsp;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 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:&amp;nbsp;the &lt;code&gt;position&lt;/code&gt; property, which let you place elements at precise coordinates, and&amp;nbsp;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!&amp;nbsp;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&amp;nbsp;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&amp;nbsp;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,&amp;nbsp;so &lt;code&gt;a:hover&lt;/code&gt; would be treated&amp;nbsp;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&amp;nbsp;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&amp;nbsp;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&amp;nbsp;like &lt;code&gt;#id&lt;/code&gt;, but&amp;nbsp;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&amp;nbsp;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&amp;nbsp;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 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 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;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="linenos"&gt;1&lt;/span&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="linenos"&gt;2&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="linenos"&gt;3&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;/code&gt;&lt;/pre&gt;&lt;/div&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&amp;nbsp;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&amp;nbsp;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;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="linenos"&gt;1&lt;/span&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;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&amp;nbsp;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 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 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;&amp;nbsp;from &lt;code&gt;&amp;lt;p&amp;gt;&lt;/code&gt; tags, also contains&amp;nbsp;surprise &lt;code&gt;&amp;lt;em&amp;gt;&lt;/code&gt;s within body text and the&amp;nbsp;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&amp;nbsp;long &lt;code&gt;&amp;lt;BODY&amp;gt;&lt;/code&gt;,&amp;nbsp;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&amp;nbsp;like &lt;code&gt;&amp;lt;p&amp;gt;...&amp;lt;/p&amp;gt;&lt;/code&gt; and markers&amp;nbsp;like &lt;code&gt;&amp;lt;br&amp;gt;&lt;/code&gt;.  Since&amp;nbsp;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&amp;nbsp;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&amp;nbsp;write &lt;code&gt;&amp;lt;br/&amp;gt;&lt;/code&gt; to&amp;nbsp;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&amp;nbsp;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&amp;nbsp;that &lt;code&gt;&amp;lt;script/&amp;gt;&lt;/code&gt; is an&amp;nbsp;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 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&amp;nbsp;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,&amp;nbsp;really: &lt;code&gt;float&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;I said&amp;nbsp;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;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="linenos"&gt;1&lt;/span&gt;+---------+
&lt;span class="linenos"&gt;2&lt;/span&gt;| sidebar | Hello, and welcome to my website!
&lt;span class="linenos"&gt;3&lt;/span&gt;|         |
&lt;span class="linenos"&gt;4&lt;/span&gt;+---------+
&lt;/code&gt;&lt;/pre&gt;&lt;/div&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;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="linenos"&gt;1&lt;/span&gt;+---------+ +-----------------------------------+
&lt;span class="linenos"&gt;2&lt;/span&gt;| sidebar | | Hello, and welcome to my website! |
&lt;span class="linenos"&gt;3&lt;/span&gt;|         | |                                   |
&lt;span class="linenos"&gt;4&lt;/span&gt;+---------+ | Here&amp;#39;s a longer paragraph to show |
&lt;span class="linenos"&gt;5&lt;/span&gt;            | that my galaxy brain CSS float    |
&lt;span class="linenos"&gt;6&lt;/span&gt;            | nonsense prevents text wrap.      |
&lt;span class="linenos"&gt;7&lt;/span&gt;            +-----------------------------------+
&lt;/code&gt;&lt;/pre&gt;&lt;/div&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&amp;nbsp;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 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;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="linenos"&gt;1&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="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="linenos"&gt;2&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="linenos"&gt;3&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="linenos"&gt;4&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="linenos"&gt;5&lt;/span&gt;    ...
&lt;span class="linenos"&gt;6&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;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,&amp;nbsp;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&amp;nbsp;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&amp;nbsp;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&amp;nbsp;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;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="linenos"&gt;1&lt;/span&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="linenos"&gt;2&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="linenos"&gt;3&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="linenos"&gt;4&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="linenos"&gt;5&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&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&amp;nbsp;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&amp;nbsp;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 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&amp;nbsp;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 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;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="linenos"&gt;1&lt;/span&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;/code&gt;&lt;/pre&gt;&lt;/div&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;&amp;nbsp;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&amp;nbsp;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&amp;nbsp;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&amp;nbsp;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&amp;nbsp;the &lt;code&gt;&amp;gt;&lt;/code&gt; child selector, so you could write stuff&amp;nbsp;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&amp;nbsp;and &lt;code&gt;inline-block&lt;/code&gt;:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="linenos"&gt;1&lt;/span&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="linenos"&gt;2&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="linenos"&gt;3&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="linenos"&gt;4&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="linenos"&gt;5&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="linenos"&gt;6&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;The general idea&amp;nbsp;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;&amp;nbsp;support &lt;code&gt;inline-block&lt;/code&gt;, but only on elements that were&amp;nbsp;naturally &lt;code&gt;inline&lt;/code&gt; — ones&amp;nbsp;like &lt;code&gt;&amp;lt;b&amp;gt;&lt;/code&gt; and &lt;code&gt;&amp;lt;i&amp;gt;&lt;/code&gt;,&amp;nbsp;not &lt;code&gt;&amp;lt;li&amp;gt;&lt;/code&gt;.  So, not ones you&amp;#8217;d actually want (or think) to&amp;nbsp;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&amp;nbsp;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;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="linenos"&gt;1&lt;/span&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="linenos"&gt;2&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="linenos"&gt;3&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="linenos"&gt;4&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="linenos"&gt;5&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="linenos"&gt;6&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="linenos"&gt;7&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="linenos"&gt;8&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&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.)&amp;nbsp;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.&amp;nbsp;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&amp;nbsp;an &lt;code&gt;inline&lt;/code&gt; element that has &amp;#8220;layout&amp;#8221; roughly like&amp;nbsp;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&amp;nbsp;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;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="linenos"&gt; 1&lt;/span&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="linenos"&gt; 2&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="linenos"&gt; 3&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="linenos"&gt; 4&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="linenos"&gt; 5&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="linenos"&gt; 6&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="linenos"&gt; 7&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="linenos"&gt; 8&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="linenos"&gt; 9&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="linenos"&gt;10&lt;/span&gt;&lt;span class="c"&gt;/* start commented backslash hack \*/&lt;/span&gt;
&lt;span class="linenos"&gt;11&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="linenos"&gt;12&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="linenos"&gt;13&lt;/span&gt;&lt;span class="c"&gt;/* close commented backslash hack */&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&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&amp;nbsp;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&amp;nbsp;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, 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&amp;nbsp;with &lt;code&gt;border-radius&lt;/code&gt;.  Specifically,&amp;nbsp;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&amp;nbsp;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&amp;nbsp;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&amp;nbsp;by &lt;code&gt;border-radius&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Gradients, provided&amp;nbsp;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&amp;nbsp;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 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&amp;nbsp;had &lt;code&gt;-moz-border-radius&lt;/code&gt;, so when Safari implemented it, it was&amp;nbsp;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&amp;nbsp;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;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="linenos"&gt;1&lt;/span&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="linenos"&gt;2&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="linenos"&gt;3&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="linenos"&gt;4&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="linenos"&gt;5&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&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&amp;nbsp;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&amp;nbsp;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&amp;nbsp;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&amp;nbsp;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;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="linenos"&gt;1&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="p"&gt;{&lt;/span&gt;
&lt;span class="linenos"&gt;2&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="linenos"&gt;3&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="linenos"&gt;4&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="linenos"&gt;5&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="linenos"&gt;6&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="linenos"&gt;7&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;This is miraculous.  I forgot all&amp;nbsp;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 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&amp;nbsp;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&amp;nbsp;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&amp;nbsp;to &lt;code&gt;min-content&lt;/code&gt;, &lt;code&gt;max-content&lt;/code&gt;, or&amp;nbsp;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&amp;nbsp;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&amp;nbsp;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&amp;nbsp;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&amp;nbsp;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&amp;nbsp;a &lt;code&gt;blur()&lt;/code&gt; and&amp;nbsp;a &lt;code&gt;drop-shadow()&lt;/code&gt; (which,&amp;nbsp;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&amp;nbsp;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&amp;nbsp;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,&amp;nbsp;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&amp;nbsp;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&amp;nbsp;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 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&amp;nbsp;children.  &lt;code&gt;A ~ B&lt;/code&gt; selects&amp;nbsp;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&amp;nbsp;is &lt;code&gt;input[type=checkbox]&lt;/code&gt;, though you can also do interesting things with matching parts&amp;nbsp;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&amp;nbsp;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,&amp;nbsp;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&amp;nbsp;shows &lt;code&gt;index.html#foo&lt;/code&gt;, this selects the element whose &lt;span class="caps"&gt;ID&lt;/span&gt;&amp;nbsp;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;&amp;nbsp;as &lt;code&gt;#rrggbbaa&lt;/code&gt; or &lt;code&gt;#rgba&lt;/code&gt;, as well as using&amp;nbsp;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&amp;nbsp;the &lt;code&gt;turn&lt;/code&gt; unit.  Of&amp;nbsp;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&amp;nbsp;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;,&amp;nbsp;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 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;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="linenos"&gt;1&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="p"&gt;{&lt;/span&gt;
&lt;span class="linenos"&gt;2&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="linenos"&gt;3&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="linenos"&gt;4&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&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&amp;nbsp;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 —&amp;nbsp;a &lt;code&gt;&amp;lt;dl&amp;gt;&lt;/code&gt; contains any number&amp;nbsp;of &lt;code&gt;&amp;lt;dt&amp;gt;&lt;/code&gt;s followed by any number&amp;nbsp;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&amp;nbsp;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&amp;nbsp;the &lt;code&gt;&amp;lt;dt&amp;gt;&lt;/code&gt;s to go in the first column&amp;nbsp;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;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="linenos"&gt; 1&lt;/span&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="linenos"&gt; 2&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="linenos"&gt; 3&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="linenos"&gt; 4&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="linenos"&gt; 5&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="linenos"&gt; 6&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="linenos"&gt; 7&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="linenos"&gt; 8&lt;/span&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="linenos"&gt; 9&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="linenos"&gt;10&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="linenos"&gt;11&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="linenos"&gt;12&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="linenos"&gt;13&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="linenos"&gt;14&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="linenos"&gt;15&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="linenos"&gt;16&lt;/span&gt;&lt;span class="c"&gt;/* ... etc ... */&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&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 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&amp;nbsp;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 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;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="linenos"&gt;1&lt;/span&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="linenos"&gt;2&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="linenos"&gt;3&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;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&amp;nbsp;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;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="linenos"&gt;1&lt;/span&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;p&gt;&amp;#8230;then &lt;span class="caps"&gt;CSS&lt;/span&gt; has no way to target either&amp;nbsp;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&amp;nbsp;custom &lt;code&gt;:has()&lt;/code&gt; pseudo-class, which could be used to express&amp;nbsp;this:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="linenos"&gt;1&lt;/span&gt;&lt;span class="c"&gt;/* checkbox label turns bold when checked */&lt;/span&gt;
&lt;span class="linenos"&gt;2&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="linenos"&gt;3&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="linenos"&gt;4&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&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;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="linenos"&gt;1&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;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="linenos"&gt;2&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="linenos"&gt;3&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Then later, they introduced&amp;nbsp;a &lt;code&gt;!&lt;/code&gt; prefix&amp;nbsp;instead:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="linenos"&gt;1&lt;/span&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="linenos"&gt;2&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="linenos"&gt;3&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&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&amp;nbsp;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 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="articles"></category><category term="tech"></category><category term="web"></category></entry><entry><title>Tech wishes for 2018</title><link href="https://eev.ee/blog/2018/02/18/tech-wishes-for-2018/" rel="alternate"></link><published>2018-02-18T13:03:00-08:00</published><updated>2018-02-18T13:03:00-08:00</updated><author><name>Eevee</name></author><id>tag:eev.ee,2018-02-18:/blog/2018/02/18/tech-wishes-for-2018/</id><summary type="html">&lt;p&gt;Anonymous asks, via money:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;What would you like to see happen in tech in 2018?&lt;/p&gt;
&lt;p&gt;(answer can be technical, social, political, combination, whatever)&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Hmm.&lt;/p&gt;
</summary><content type="html">&lt;p&gt;Anonymous asks, via&amp;nbsp;money:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;What would you like to see happen in tech in&amp;nbsp;2018?&lt;/p&gt;
&lt;p&gt;(answer can be technical, social, political, combination,&amp;nbsp;whatever)&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Hmm.&lt;/p&gt;


&lt;h2 id="less-of-this"&gt;&lt;a class="toclink" href="#less-of-this"&gt;Less of this&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;I&amp;#8217;m not really qualified to speak in depth about either of these things, but let me put my foot in my mouth&amp;nbsp;anyway:&lt;/p&gt;
&lt;h3 id="the-blockchaintm"&gt;&lt;a class="toclink" href="#the-blockchaintm"&gt;The Blockchain™&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Bitcoin was a neat idea.  No, really!  Decentralization is cool.  Overhauling our terrible financial infrastructure is cool.  Hash functions are&amp;nbsp;cool.&lt;/p&gt;
&lt;p&gt;Unfortunately, it seems to have devolved into mostly a get-rich-quick scheme for nerds, and by nearly any measure it&amp;#8217;s turning into a spectacular catastrophe.  Its &amp;#8220;success&amp;#8221; is measured in how much a bitcoin is worth &lt;em&gt;in &lt;span class="caps"&gt;US&lt;/span&gt; dollars&lt;/em&gt;, which is pretty close to an admission from its own investors that its only value is in converting back to &amp;#8220;real&amp;#8221; money — all while that same &amp;#8220;success&amp;#8221; is making it &lt;em&gt;less&lt;/em&gt; useful as a distinct&amp;nbsp;currency.&lt;/p&gt;
&lt;p&gt;Blah, blah, everyone already knows&amp;nbsp;this.&lt;/p&gt;
&lt;p&gt;What concerns me slightly more is the gold rush hype cycle, which is putting cryptocurrency and &amp;#8220;blockchain&amp;#8221; in the news and lending it all legitimacy.  People have raked in &lt;em&gt;millions of dollars&lt;/em&gt; on ICOs of novel coins I&amp;#8217;ve never heard mentioned again.  (Note: again, that value is measured &lt;em&gt;in dollars&lt;/em&gt;.)  Most likely, none of the investors will see any return whatsoever on that money.  They &lt;em&gt;can&amp;#8217;t&lt;/em&gt;, really, unless a coin actually takes off &lt;em&gt;as a currency&lt;/em&gt;, and that seems at odds with speculative investing since everyone either wants to hoard or ditch their coins.  When the coins have no value themselves, the money can only come from other investors, and eventually the hype winds down and you run out of other&amp;nbsp;investors.&lt;/p&gt;
&lt;p&gt;I fear this will hurt a &lt;em&gt;lot&lt;/em&gt; of people before it&amp;#8217;s over, so I&amp;#8217;d like for it to be over as soon as&amp;nbsp;possible.&lt;/p&gt;
&lt;hr /&gt;
&lt;p&gt;That said, the hype itself has gotten way out of hand too.  First it was the obsession with &amp;#8220;blockchain&amp;#8221; like it&amp;#8217;s a revolutionary technology, but hey, &lt;em&gt;Git&lt;/em&gt; is a fucking blockchain.  The novel part is the way it handles distributed &lt;em&gt;consensus&lt;/em&gt; (which in Git is basically left for you to figure out), and that&amp;#8217;s uniquely important to currency because you want to be pretty sure that money doesn&amp;#8217;t get duplicated or lost when moved&amp;nbsp;around.&lt;/p&gt;
&lt;p&gt;But now we have startups trying to use blockchains for website backends and file storage and who knows what else?  Why?  What advantage does this have?  When you say &amp;#8220;blockchain&amp;#8221;, I hear &amp;#8220;single Git repository&amp;#8221; — so when you say &amp;#8220;email on the blockchain&amp;#8221;, I have an&amp;nbsp;aneurysm.&lt;/p&gt;
&lt;p&gt;Bitcoin seems to have sparked imagination in large part because it&amp;#8217;s decentralized, but I&amp;#8217;d argue it&amp;#8217;s actually a pretty &lt;em&gt;bad&lt;/em&gt; example of a decentralized network, since &lt;em&gt;people keep forking it&lt;/em&gt;.  The ability to fork is a feature, sure, but the trouble here is that the Bitcoin family has no notion of &lt;em&gt;federation&lt;/em&gt; — there is &lt;em&gt;one&lt;/em&gt; canonical Bitcoin ledger and it has no notion of communication with any other.  That&amp;#8217;s what you want for currency, not necessarily other applications.  (Bitcoin also &lt;em&gt;incentivizes&lt;/em&gt; &lt;a href="https://gizmodo.com/atari-is-launching-a-cryptocurrency-because-thats-just-1823042620"&gt;frivolous forking&lt;/a&gt; by giving the creator an initial pile of coins to keep and&amp;nbsp;sell.)&lt;/p&gt;
&lt;p&gt;And federation is much more interesting than decentralization!  Federation gives us email and the web.  Federation means I can set up my own instance with my own rules and &lt;em&gt;still&lt;/em&gt; be able to meaningfully communicate with the rest of the network.  Federation has some amount of tolerance for changes to the protocol, so such changes are more flexible and rely more heavily on&amp;nbsp;consensus.&lt;/p&gt;
&lt;p&gt;Federation is fantastic, and it feels like a massive tragedy that this rekindled interest in decentralization is mostly focused on peer-to-peer networks, which do little to address our current problems with centralized&amp;nbsp;platforms.&lt;/p&gt;
&lt;p&gt;And hey, you know what else is federated?  &lt;strong&gt;&lt;em&gt;Banks&lt;/em&gt;&lt;/strong&gt;.&lt;/p&gt;
&lt;h3 id="ai"&gt;&lt;a class="toclink" href="#ai"&gt;AI&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Again, the tech is cool and all, but the marketing hype is getting way out of&amp;nbsp;hand.&lt;/p&gt;
&lt;p&gt;Maybe what I really want from 2018 is less&amp;nbsp;marketing?&lt;/p&gt;
&lt;p&gt;For one, I&amp;#8217;ve seen a &lt;em&gt;huge&lt;/em&gt; uptick in uncritically referring to any software that creates or classifies creative work as &amp;#8220;&lt;span class="caps"&gt;AI&lt;/span&gt;&amp;#8221;.  Can we…  can we not.  It&amp;#8217;s not &lt;span class="caps"&gt;AI&lt;/span&gt;.  Yes, yes, nerds, I don&amp;#8217;t care about the hair-splitting about the nature of intelligence — you &lt;em&gt;know&lt;/em&gt; that when we hear &amp;#8220;&lt;span class="caps"&gt;AI&lt;/span&gt;&amp;#8221; we think of a human-like self-aware intelligence.  But we&amp;#8217;re applying it to stuff like a weird dog generator.  Or to whatever neural network a website threw into production this&amp;nbsp;week.&lt;/p&gt;
&lt;p&gt;And this is &lt;em&gt;dangerously&lt;/em&gt; misleading — we already had massive tech companies scapegoating The Algorithm™ for the poor behavior of their software, and now we&amp;#8217;re talking about those algorithms as though they were self-aware, untouchable, untameable, unknowable entities of pure chaos whose decisions we are arbitrarily bound to.  Ancient, powerful gods who exist just outside human comprehension or&amp;nbsp;law.&lt;/p&gt;
&lt;p&gt;It&amp;#8217;s weird to see this stuff appear in consumer products so quickly, too.  It feels quick, anyway.  The latest iPhone can unlock via facial recognition, right?  I&amp;#8217;m sure a lot of effort was put into ensuring that the same person&amp;#8217;s face would always be recognized…  but how confident are we that &lt;em&gt;other&lt;/em&gt; faces &lt;em&gt;won&amp;#8217;t&lt;/em&gt; be recognized?  I admit I don&amp;#8217;t follow all this super closely, so I may be imagining a non-problem, but I &lt;em&gt;do&lt;/em&gt; know that humans are remarkably bad at checking for negative&amp;nbsp;cases.&lt;/p&gt;
&lt;p&gt;Hell, take the recurring problem of major platforms like Twitter and YouTube classifying anything mentioning &amp;#8220;bisexual&amp;#8221; as pornographic — because the word is also used as a porn genre, and someone threw a list of porn terms into a filter without thinking too hard about it.  That&amp;#8217;s just a &lt;em&gt;word list&lt;/em&gt;, a fairly simple thing that any human can review; but suddenly we&amp;#8217;re confident in opaque networks of inferred&amp;nbsp;details?&lt;/p&gt;
&lt;p&gt;I don&amp;#8217;t know.  &amp;#8220;Traditional&amp;#8221; classification and generation are much more comforting, since they&amp;#8217;re a set of fairly abstract rules that can be examined and followed.  Machine learning, as I understand it, is less about rules and much more about pattern-matching; it&amp;#8217;s &lt;em&gt;built out of&lt;/em&gt; the fingerprints of the stuff it&amp;#8217;s trained on.  Surely that&amp;#8217;s just begging for tons of edge cases.  They&amp;#8217;re practically &lt;em&gt;made of&lt;/em&gt; edge&amp;nbsp;cases.&lt;/p&gt;
&lt;hr /&gt;
&lt;p&gt;I&amp;#8217;m reminded of a point I saw made a few days ago on Twitter, something I&amp;#8217;d never thought about but should have.  TurnItIn is a service for universities that checks whether students&amp;#8217; papers match any others, in order to detect cheating.  But this is a &lt;em&gt;paid&lt;/em&gt; service, one that &lt;em&gt;fundamentally hinges&lt;/em&gt; on its corpus: a large collection of existing student papers.  So students pay money to attend school, where they&amp;#8217;re &lt;em&gt;required&lt;/em&gt; to let their work be given to a third-party company, which then profits off of it?  What kind of a goofy business model is&amp;nbsp;this?&lt;/p&gt;
&lt;p&gt;And my thoughts turn to machine learning, which is fundamentally different from an algorithm you can simply copy from a paper, because it&amp;#8217;s all about the training data.  And to get good results, you need a &lt;em&gt;lot&lt;/em&gt; of training data.  Where is that all coming from?  How many for-profit companies are setting a neural network loose on the web — on millions of people&amp;#8217;s work — and then turning around and selling the result as a&amp;nbsp;product?&lt;/p&gt;
&lt;p&gt;This is really a question of how intellectual property works in the internet era, and it continues our proud decades-long tradition of just kinda doing whatever we want without thinking about it too much.  Nothing if not&amp;nbsp;consistent.&lt;/p&gt;
&lt;h2 id="more-of-this"&gt;&lt;a class="toclink" href="#more-of-this"&gt;More of this&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;A bit tougher, since computers are pretty alright now and everything continues to chug along.  Maybe we should just quit while we&amp;#8217;re ahead.  There&amp;#8217;s some real pie-in-the-sky stuff that would be nice, but it certainly won&amp;#8217;t happen within a year, and may never happen except in some horrific Algorithmic™ form designed by people that don&amp;#8217;t know anything about the problem space and only works 60% of the time but is treated as though it were&amp;nbsp;bulletproof.&lt;/p&gt;
&lt;h3 id="federation"&gt;&lt;a class="toclink" href="#federation"&gt;Federation&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;The giants are getting more giant.  Maybe too giant?  Granted, it could be much worse than Google and Amazon — it could be&amp;nbsp;Apple!&lt;/p&gt;
&lt;p&gt;Amazon has its own delivery service and brick-and-mortar stores now, as well as providing the plumbing for vast amounts of the web.  They&amp;#8217;re not doing anything particularly outrageous, but they kind of &lt;em&gt;loom&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;Ad company Google just put ad blocking in its majority-share browser — albeit for the ambiguously-noble goal of only blocking &lt;em&gt;obnoxious&lt;/em&gt; ads so that people will be less inclined to install a &lt;em&gt;blanket&lt;/em&gt; ad&amp;nbsp;blocker.&lt;/p&gt;
&lt;p&gt;Twitter is kind of a nightmare but no one wants to leave.  I keep trying to &lt;a href="https://mastodon.social/@eevee"&gt;use Mastodon&lt;/a&gt; as well, but I always forget about it after a day,&amp;nbsp;whoops.&lt;/p&gt;
&lt;p&gt;Facebook sounds like a &lt;em&gt;total&lt;/em&gt; nightmare but no one wants to leave that either, because normies don&amp;#8217;t use anything else, which is itself direly&amp;nbsp;concerning.&lt;/p&gt;
&lt;p&gt;&lt;span class="caps"&gt;IRC&lt;/span&gt; is rapidly bleeding mindshare to Slack and Discord, both of which are far better at the things &lt;span class="caps"&gt;IRC&lt;/span&gt; sadly never tried to do and absolutely terrible at the exact things &lt;span class="caps"&gt;IRC&lt;/span&gt; excels&amp;nbsp;at.&lt;/p&gt;
&lt;p&gt;The problem is the same as ever: there&amp;#8217;s no incentive to interoperate.  There&amp;#8217;s no fundamental technical reason why Twitter and Tumblr and MySpace and Facebook can&amp;#8217;t intermingle their posts; they just don&amp;#8217;t, because why would they bother?  It&amp;#8217;s extra work that makes it easier for people to &lt;em&gt;not&lt;/em&gt; use your&amp;nbsp;ecosystem.&lt;/p&gt;
&lt;p&gt;I don&amp;#8217;t know what can be done about that, except that hope for a really big player to decide to play nice out of the kindness of their heart.  The really big federated success stories — say, the web — mostly won out because they came along &lt;em&gt;first&lt;/em&gt;.  At this point, how does a federated social network take over?  I don&amp;#8217;t&amp;nbsp;know.&lt;/p&gt;
&lt;h3 id="social-progress"&gt;&lt;a class="toclink" href="#social-progress"&gt;Social progress&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;I… don&amp;#8217;t really have a solid grasp on what&amp;#8217;s happening in tech socially at the moment.  I&amp;#8217;ve drifted a bit away from the &lt;em&gt;industry&lt;/em&gt; part, which is where that all tends to come up.  I have the vague sense that things are improving, but that might just be because the Rust community is the one I hear the most about, and it puts a lot of effort into being inclusive and&amp;nbsp;welcoming.&lt;/p&gt;
&lt;p&gt;So… more projects should be like Rust?  Do whatever Rust is doing?  And not so much what Linus is&amp;nbsp;doing.&lt;/p&gt;
&lt;h3 id="open-source-funding"&gt;&lt;a class="toclink" href="#open-source-funding"&gt;Open source funding&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;I haven&amp;#8217;t heard this brought up much lately, but it would still be nice to see.  The Bay Area &lt;em&gt;runs&lt;/em&gt; on open source and is raking in zillions of dollars on its back; pump some of that cash back into the ecosystem,&amp;nbsp;somehow.&lt;/p&gt;
&lt;p&gt;I&amp;#8217;ve seen a couple open source projects on Patreon, which is fantastic, but feels like a very &lt;em&gt;small&lt;/em&gt; solution given how much money is flowing through the commercial tech&amp;nbsp;industry.&lt;/p&gt;
&lt;h3 id="ad-blocking"&gt;&lt;a class="toclink" href="#ad-blocking"&gt;Ad blocking&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Nice.  Fuck&amp;nbsp;ads.&lt;/p&gt;
&lt;p&gt;One might wonder where the money to host a website comes from, then?  I don&amp;#8217;t know.  Maybe we should loop this in with the above thing and find a more informal way to pay people for the stuff they make when we find it useful, without the financial and cognitive overhead of A Transaction or Giving Someone My Damn Credit Card Number.  You know, something like Bitco—  ah,&amp;nbsp;fuck.&lt;/p&gt;
&lt;h3 id="year-of-the-linux-desktop"&gt;&lt;a class="toclink" href="#year-of-the-linux-desktop"&gt;Year of the Linux Desktop&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;I don&amp;#8217;t know.  What are we working on at the moment?  Wayland?  Do Wayland, I guess.  Oh, and hi-&lt;span class="caps"&gt;DPI&lt;/span&gt;, which I hear sucks.  And please fix my sound drivers so PulseAudio stops blaming them when it fucks&amp;nbsp;up.&lt;/p&gt;</content><category term="articles"></category><category term="tech"></category><category term="patreon"></category></entry><entry><title>SUPER game night 3: GAMES MADE QUICK??? 2.0</title><link href="https://eev.ee/blog/2018/01/23/super-game-night-3-games-made-quick-2-0/" rel="alternate"></link><published>2018-01-23T23:03:00-08:00</published><updated>2018-01-23T23:03:00-08:00</updated><author><name>Eevee</name></author><id>tag:eev.ee,2018-01-23:/blog/2018/01/23/super-game-night-3-games-made-quick-2-0/</id><summary type="html">&lt;p&gt;Game night continues with a smorgasbord of games from my recent game jam, &lt;a href="https://itch.io/jam/games-made-quick-2"&gt;&lt;span class="caps"&gt;GAMES&lt;/span&gt; &lt;span class="caps"&gt;MADE&lt;/span&gt; &lt;span class="caps"&gt;QUICK&lt;/span&gt;??? 2.0&lt;/a&gt;!&lt;/p&gt;
&lt;p&gt;The idea was to make a game in only a week while watching &lt;span class="caps"&gt;AGDQ&lt;/span&gt;, as an alternative to doing absolutely nothing for a week while watching &lt;span class="caps"&gt;AGDQ&lt;/span&gt;.  (I didn’t submit a game myself; I was chugging along on my Anise game, which isn’t finished yet.)&lt;/p&gt;
&lt;p&gt;I can’t very well run a game jam and not play any of the games, so here’s some of them in no particular order! Enjoy!&lt;/p&gt;
&lt;p&gt;&lt;em&gt;These are impressions, not reviews.  I try to avoid major/ending spoilers, but big plot points do tend to leave impressions.&lt;/em&gt;&lt;/p&gt;
</summary><content type="html">&lt;p&gt;Game night continues with a smorgasbord of games from my recent game jam, &lt;a href="https://itch.io/jam/games-made-quick-2"&gt;&lt;span class="caps"&gt;GAMES&lt;/span&gt; &lt;span class="caps"&gt;MADE&lt;/span&gt; &lt;span class="caps"&gt;QUICK&lt;/span&gt;??? 2.0&lt;/a&gt;!&lt;/p&gt;
&lt;p&gt;The idea was to make a game in only a week while watching &lt;span class="caps"&gt;AGDQ&lt;/span&gt;, as an alternative to doing absolutely nothing for a week while watching &lt;span class="caps"&gt;AGDQ&lt;/span&gt;.  (I didn&amp;#8217;t submit a game myself; I was chugging along on my Anise game, which isn&amp;#8217;t finished&amp;nbsp;yet.)&lt;/p&gt;
&lt;p&gt;I can&amp;#8217;t very well run a game jam and not play any of the games, so here&amp;#8217;s some of them in no particular order!&amp;nbsp;Enjoy!&lt;/p&gt;
&lt;p&gt;&lt;em&gt;These are impressions, not reviews.  I try to avoid major/ending spoilers, but big plot points do tend to leave&amp;nbsp;impressions.&lt;/em&gt;&lt;/p&gt;


&lt;h2 id="weather-quest-by-timlmul"&gt;&lt;a class="toclink" href="#weather-quest-by-timlmul"&gt;Weather Quest, by timlmul&lt;/a&gt;&lt;/h2&gt;
&lt;div class="prose-full-illustration"&gt;
&lt;img src="https://eev.ee/media/game-night/games-made-quick-2/weather-quest.png" alt=""&gt;
&lt;/div&gt;

&lt;p&gt;&lt;center&gt;&lt;em&gt;short · rpg · jan 2017 · (lin)/mac/win · free on &lt;a href="https://timlmul.itch.io/weather-quest"&gt;itch&lt;/a&gt; · &lt;a href="https://itch.io/jam/games-made-quick-2/rate/214063"&gt;jam entry&lt;/a&gt;&lt;/em&gt;&lt;/center&gt;&lt;/p&gt;
&lt;p&gt;Weather Quest is its author&amp;#8217;s first shipped game, written completely from scratch (the only vendored code is a micro &lt;span class="caps"&gt;OO&lt;/span&gt; base).  It&amp;#8217;s very short, but as someone who has also written LÖVE games completely from scratch, I can attest that producing something this game-like in a week is a &lt;em&gt;fucking miracle&lt;/em&gt;.&amp;nbsp;Bravo!&lt;/p&gt;
&lt;p&gt;For reference, a week into my first foray, I think I was probably still writing my own Tiled importer like an&amp;nbsp;idiot.&lt;/p&gt;
&lt;p&gt;Only Mac and Windows builds are on itch, but it&amp;#8217;s a LÖVE game, so Linux folks can just grab a zip from &lt;a href="https://github.com/timothymullen/weatherquest"&gt;GitHub&lt;/a&gt; and throw that&amp;nbsp;at &lt;code&gt;love&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;&lt;span class="caps"&gt;FINAL&lt;/span&gt; &lt;span class="caps"&gt;SCORE&lt;/span&gt;:&lt;/strong&gt;&amp;nbsp;⛅☔☀&lt;/p&gt;
&lt;h2 id="pancake-numbers-simulator-by-anoraktheprimordial"&gt;&lt;a class="toclink" href="#pancake-numbers-simulator-by-anoraktheprimordial"&gt;Pancake Numbers Simulator, by AnorakThePrimordial&lt;/a&gt;&lt;/h2&gt;
&lt;div class="prose-full-illustration"&gt;
&lt;img src="https://eev.ee/media/game-night/games-made-quick-2/pancake-numbers-simulator.png" alt=""&gt;
&lt;/div&gt;

&lt;p&gt;&lt;center&gt;&lt;em&gt;short · sim · jan 2017 · lin/mac/win · free on &lt;a href="https://anoraktheprimordial.itch.io/pancake-numbers-simulator"&gt;itch&lt;/a&gt; · &lt;a href="https://itch.io/jam/games-made-quick-2/rate/213239"&gt;jam entry&lt;/a&gt;&lt;/em&gt;&lt;/center&gt;&lt;/p&gt;
&lt;p&gt;Given a stack of N pancakes (of all different sizes and in no particular order), the Nth pancake number is the &lt;em&gt;most&lt;/em&gt; flips you could possibly need to sort the pancakes in order with the smallest on top.  A &amp;#8220;flip&amp;#8221; is sticking a spatula under one of the pancakes and flipping the whole sub-stack over.  There&amp;#8217;s, ah, &lt;a href="https://www.youtube.com/watch?v=m3drS_8BpU0"&gt;a video&lt;/a&gt; embedded on the game page with some&amp;nbsp;visuals.&lt;/p&gt;
&lt;p&gt;Anyway, this game lets you simulate sorting a stack via pancake flipping, which is surprisingly satisfying!  I enjoy cleaning up little simulated messes, such as…  incorrectly-sorted pancakes, I&amp;nbsp;guess?&lt;/p&gt;
&lt;p&gt;This probably doesn&amp;#8217;t work too well as a simulator for solving the &lt;em&gt;general&lt;/em&gt; problem — you&amp;#8217;d have to find an optimal solution for &lt;em&gt;every&lt;/em&gt; permutation of N pancakes to be sure you were right.  But it&amp;#8217;s a nice interactive illustration of the problem, and if you know the pancake number for your stack size of choice (which I wish the game told you — for seven pancakes, it&amp;#8217;s 8), then trying to restore a stack in that many moves makes for a nice quick&amp;nbsp;puzzle.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;&lt;span class="caps"&gt;FINAL&lt;/span&gt; &lt;span class="caps"&gt;SCORE&lt;/span&gt;:&lt;/strong&gt; &lt;span class="math"&gt;\(\frac{18}{11}\)&lt;/span&gt;&lt;/p&gt;
&lt;h2 id="framed-animals-by-chridd"&gt;&lt;a class="toclink" href="#framed-animals-by-chridd"&gt;Framed Animals, by chridd&lt;/a&gt;&lt;/h2&gt;
&lt;div class="prose-full-illustration"&gt;
&lt;img src="https://eev.ee/media/game-night/games-made-quick-2/framed-animals.png" alt=""&gt;
&lt;/div&gt;

&lt;p&gt;&lt;center&gt;&lt;em&gt;short · metroidvania · jan 2017 · web/win · free on &lt;a href="https://chridd.itch.io/framed-animals"&gt;itch&lt;/a&gt; · &lt;a href="https://itch.io/jam/games-made-quick-2/rate/214158"&gt;jam entry&lt;/a&gt;&lt;/em&gt;&lt;/center&gt;&lt;/p&gt;
&lt;p&gt;The concept here was to &lt;em&gt;kill the frames, save the animals&lt;/em&gt;, which is a delightfully literal riff on a long-running &lt;span class="caps"&gt;AGDQ&lt;/span&gt;/&lt;span class="caps"&gt;SGDQ&lt;/span&gt; donation incentive — people vote with their dollars to decide whether Super Metroid speedrunners go out of their way to free the critters who show you how to walljump and shinespark.  Super Metroid didn&amp;#8217;t have a showing at this year&amp;#8217;s &lt;span class="caps"&gt;AGDQ&lt;/span&gt;, and so we have this game&amp;nbsp;instead.&lt;/p&gt;
&lt;p&gt;It&amp;#8217;s rough, but clever, and I got really into it pretty quickly — each animal you save gives you a new ability (in true Metroid style), and you get to test that ability out by playing &lt;em&gt;as the animal&lt;/em&gt;, with only that ability and no others, to get yourself back to the most recent save&amp;nbsp;point.&lt;/p&gt;
&lt;p&gt;I did, tragically, manage to get myself stuck near what I think was about to be the end of the game, so some of the animals will remain framed forever.  What an unsatisfying&amp;nbsp;conclusion.&lt;/p&gt;
&lt;p&gt;Gravity feels a little high given the size of the screen, and like most tile-less platformers, there&amp;#8217;s not really any way to gauge how high or long your jump is before you leap.  But I&amp;#8217;m only even nitpicking because I think this is a great idea and I hope the author really &lt;em&gt;does&lt;/em&gt; keep working on&amp;nbsp;it.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;&lt;span class="caps"&gt;FINAL&lt;/span&gt; &lt;span class="caps"&gt;SCORE&lt;/span&gt;:&lt;/strong&gt;&amp;nbsp;$136,596.69&lt;/p&gt;
&lt;h2 id="battle-4-glory-by-storyteller-games"&gt;&lt;a class="toclink" href="#battle-4-glory-by-storyteller-games"&gt;Battle 4 Glory, by Storyteller Games&lt;/a&gt;&lt;/h2&gt;
&lt;div class="prose-full-illustration"&gt;
&lt;img src="https://eev.ee/media/game-night/games-made-quick-2/battle-4-glory.png" alt=""&gt;
&lt;/div&gt;

&lt;p&gt;&lt;center&gt;&lt;em&gt;short · fighter · jan 2017 · win · free on &lt;a href="https://storytellergames.itch.io/battle-4-glory"&gt;itch&lt;/a&gt; · &lt;a href="https://itch.io/jam/games-made-quick-2/rate/214091"&gt;jam entry&lt;/a&gt;&lt;/em&gt;&lt;/center&gt;&lt;/p&gt;
&lt;p&gt;This is a Smash Bros-style brawler, complete with the four players, the 2D play area in a 3D world, and the random stage obstacles showing up.  I do like the Smash style, despite not otherwise being a fan of fighting games, so it&amp;#8217;s nice to see another game chase that&amp;nbsp;aesthetic.&lt;/p&gt;
&lt;p&gt;Alas, that&amp;#8217;s about as far as it got — which is pretty far for a week of work!  I don&amp;#8217;t know what more to say, though.  The environments are neat, but unless I&amp;#8217;m missing something, the only actions at your disposal are jumping and very weak melee attacks.  I did have a good few minutes of fun fruitlessly mashing myself against the bumbling bots, as you can&amp;nbsp;see.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;&lt;span class="caps"&gt;FINAL&lt;/span&gt; &lt;span class="caps"&gt;SCORE&lt;/span&gt;:&lt;/strong&gt;&amp;nbsp;300%&lt;/p&gt;
&lt;h2 id="icnaluferu-guild-year-sixteen-by-chz"&gt;&lt;a class="toclink" href="#icnaluferu-guild-year-sixteen-by-chz"&gt;Icnaluferu Guild, Year Sixteen, by CHz&lt;/a&gt;&lt;/h2&gt;
&lt;div class="prose-full-illustration"&gt;
&lt;img src="https://eev.ee/media/game-night/games-made-quick-2/icnaluferu-guild-year-sixteen.png" alt=""&gt;
&lt;/div&gt;

&lt;p&gt;&lt;center&gt;&lt;em&gt;short · adventure · jan 2017 · web · free on &lt;a href="https://chz.itch.io/icnaluferu-guild-year-sixteen"&gt;itch&lt;/a&gt; · &lt;a href="https://itch.io/jam/games-made-quick-2/rate/213873"&gt;jam entry&lt;/a&gt;&lt;/em&gt;&lt;/center&gt;&lt;/p&gt;
&lt;p&gt;Here we have the first of several games made with &lt;a href="https://ledoux.itch.io/bitsy"&gt;bitsy&lt;/a&gt;, a micro game making tool that basically only supports walking around, talking to people, and picking up&amp;nbsp;items.&lt;/p&gt;
&lt;p&gt;I tell you this because I think half of my appreciation for this game is in the ways it wriggled against those limits to emulate a Zelda-like dungeon crawler.  &lt;em&gt;Everything&lt;/em&gt; in here is totally fake, and you can&amp;#8217;t really understand just how fake unless you&amp;#8217;ve tried to make something complicated with&amp;nbsp;bitsy.&lt;/p&gt;
&lt;p&gt;It&amp;#8217;s pretty good.  The dialogue is entertaining (the rest of your party develops distinct personalities solely through oneliners, somehow), the riffs on standard dungeon fare are charming, and the Link&amp;#8217;s Awakening-esque perspective walls around the edges of each room are fucking &lt;em&gt;glorious&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;&lt;span class="caps"&gt;FINAL&lt;/span&gt; &lt;span class="caps"&gt;SCORE&lt;/span&gt;:&lt;/strong&gt; 2&amp;nbsp;bits&lt;/p&gt;
&lt;h2 id="the-lonely-tapes-by-jthomeslice"&gt;&lt;a class="toclink" href="#the-lonely-tapes-by-jthomeslice"&gt;The Lonely Tapes, by JTHomeslice&lt;/a&gt;&lt;/h2&gt;
&lt;div class="prose-full-illustration"&gt;
&lt;img src="https://eev.ee/media/game-night/games-made-quick-2/the-lonely-tapes.png" alt=""&gt;
&lt;/div&gt;

&lt;p&gt;&lt;center&gt;&lt;em&gt;short · rpg · jan 2017 · web · free on &lt;a href="https://jthomeslice.itch.io/the-lonely-tapes"&gt;itch&lt;/a&gt; · &lt;a href="https://itch.io/jam/games-made-quick-2/rate/212755"&gt;jam entry&lt;/a&gt;&lt;/em&gt;&lt;/center&gt;&lt;/p&gt;
&lt;p&gt;Another bitsy entry, this one sees you play as a Wal— sorry, a &lt;em&gt;JogDawg&lt;/em&gt;, which has lost its cassette tapes and needs to go recover&amp;nbsp;them!&lt;/p&gt;
&lt;p&gt;(A &lt;em&gt;cassette tape&lt;/em&gt; is like a &lt;span class="caps"&gt;VHS&lt;/span&gt;, but for&amp;nbsp;music.)&lt;/p&gt;
&lt;p&gt;(A &lt;em&gt;&lt;span class="caps"&gt;VHS&lt;/span&gt;&lt;/em&gt;&amp;nbsp;is—)&lt;/p&gt;
&lt;p&gt;I have the sneaking suspicion that I missed out on some musical in-jokes, due to being uncultured swine.  I still enjoyed the game — it&amp;#8217;s always clear when someone is passionate about the thing they&amp;#8217;re writing about, and I could tell I was awash in that aura even if some of it went over my head.  You know you&amp;#8217;ve done good if someone from way outside your sphere shows up and still has a good&amp;nbsp;time.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;&lt;span class="caps"&gt;FINAL&lt;/span&gt; &lt;span class="caps"&gt;SCORE&lt;/span&gt;:&lt;/strong&gt; Nine…  Inch Nails?  They&amp;#8217;re a band, right?  God I don&amp;#8217;t know write your own damn&amp;nbsp;joke&lt;/p&gt;
&lt;h2 id="pirate-kitty-quest-by-thekoolestkid"&gt;&lt;a class="toclink" href="#pirate-kitty-quest-by-thekoolestkid"&gt;Pirate Kitty-Quest, by TheKoolestKid&lt;/a&gt;&lt;/h2&gt;
&lt;div class="prose-full-illustration"&gt;
&lt;img src="https://eev.ee/media/game-night/games-made-quick-2/pirate-kitty-quest.png" alt=""&gt;
&lt;/div&gt;

&lt;p&gt;&lt;center&gt;&lt;em&gt;short · adventure · jan 2017 · win · free on &lt;a href="https://finnrocket5.itch.io/pirate-kitty-quest"&gt;itch&lt;/a&gt; · &lt;a href="https://itch.io/jam/games-made-quick-2/rate/213885"&gt;jam entry&lt;/a&gt;&lt;/em&gt;&lt;/center&gt;&lt;/p&gt;
&lt;p&gt;I completely forgot I&amp;#8217;d even given &amp;#8220;my birthday&amp;#8221; and &amp;#8220;my cat&amp;#8221; as mostly-joking jam themes until I stumbled upon this incredible gem.  I don&amp;#8217;t think — let me just check here and — yeah no this person doesn&amp;#8217;t even follow me on Twitter.  I have no idea who they&amp;nbsp;are?&lt;/p&gt;
&lt;p&gt;&lt;span class="caps"&gt;BUT&lt;/span&gt; &lt;span class="caps"&gt;THEY&lt;/span&gt; &lt;span class="caps"&gt;MADE&lt;/span&gt; A &lt;span class="caps"&gt;GAME&lt;/span&gt; &lt;span class="caps"&gt;ABOUT&lt;/span&gt; &lt;span class="caps"&gt;ANISE&lt;/span&gt; &lt;span class="caps"&gt;AS&lt;/span&gt; A &lt;span class="caps"&gt;PIRATE&lt;/span&gt;, &lt;span class="caps"&gt;LOOKING&lt;/span&gt; &lt;span class="caps"&gt;FOR&lt;/span&gt; &lt;span class="caps"&gt;TREASURE&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;span class="caps"&gt;PIRATE&lt;/span&gt;.  &lt;span class="caps"&gt;ANISE&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;span class="caps"&gt;PIRATE&lt;/span&gt; &lt;span class="caps"&gt;ANISE&lt;/span&gt;!!!&lt;/p&gt;
&lt;p&gt;This game wins the jam, hands down.&amp;nbsp;🏆&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;&lt;span class="caps"&gt;FINAL&lt;/span&gt; &lt;span class="caps"&gt;SCORE&lt;/span&gt;:&lt;/strong&gt; Yarr, eight pieces o&amp;#8217;&amp;nbsp;eight&lt;/p&gt;
&lt;h2 id="chips-mario-by-novasquirrel"&gt;&lt;a class="toclink" href="#chips-mario-by-novasquirrel"&gt;CHIPS Mario, by NovaSquirrel&lt;/a&gt;&lt;/h2&gt;
&lt;div class="prose-full-illustration"&gt;
&lt;img src="https://eev.ee/media/game-night/games-made-quick-2/chips-mario.png" alt=""&gt;
&lt;/div&gt;

&lt;p&gt;&lt;center&gt;&lt;em&gt;short · platformer · jan 2017 · (lin/mac)/win · free on &lt;a href="https://novasquirrel.itch.io/chips-mario"&gt;itch&lt;/a&gt; · &lt;a href="https://itch.io/jam/games-made-quick-2/rate/214130"&gt;jam entry&lt;/a&gt;&lt;/em&gt;&lt;/center&gt;&lt;/p&gt;
&lt;p&gt;You see this?  This is &lt;em&gt;fucking witchcraft&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;This game is made with MegaZeux.  MegaZeux games look like &lt;a href="http://vault.digitalmzx.net/show.php?id=182"&gt;&lt;span class="caps"&gt;THIS&lt;/span&gt;&lt;/a&gt;.  Text-mode, bound to a grid, with two colors per cell.  That&amp;#8217;s all you&amp;nbsp;get.&lt;/p&gt;
&lt;p&gt;Until now, apparently??  The game is a tech demo of &amp;#8220;unbound&amp;#8221; sprites, which can be drawn on top of the character grid without being aligned to it.  And apparently have looser color&amp;nbsp;restrictions.&lt;/p&gt;
&lt;p&gt;The collision is a &lt;em&gt;little&lt;/em&gt; glitchy, which isn&amp;#8217;t surprising for a MegaZeux platformer; I had some fun interactions with platforms a couple times.  But hey, goddamn, it&amp;#8217;s free-moving Mario, in MegaZeux, what the&amp;nbsp;hell.&lt;/p&gt;
&lt;p&gt;(I&amp;#8217;m looking at the most recently added games on &lt;a href="http://vault.digitalmzx.net/"&gt;DigitalMZX&lt;/a&gt; now, and I notice that not only is this game in the first slot, but NovaSquirrel&amp;#8217;s MegaZeux entry for Strawberry Jam &lt;em&gt;last February&lt;/em&gt; is still in the seventh slot.  &lt;span class="caps"&gt;RIP&lt;/span&gt;, MegaZeux.  I&amp;#8217;m surprised a major feature like this was even added if the community has largely&amp;nbsp;evaporated?)&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;&lt;span class="caps"&gt;FINAL&lt;/span&gt; &lt;span class="caps"&gt;SCORE&lt;/span&gt;:&lt;/strong&gt; n/a, disqualified for being probably summoned from the depths of&amp;nbsp;Hell&lt;/p&gt;
&lt;h2 id="d-pic-by-573-games"&gt;&lt;a class="toclink" href="#d-pic-by-573-games"&gt;d!¢&amp;lt; pic, by 573 Games&lt;/a&gt;&lt;/h2&gt;
&lt;div class="prose-full-illustration"&gt;
&lt;img src="https://eev.ee/media/game-night/games-made-quick-2/dick-pic.png" alt=""&gt;
&lt;/div&gt;

&lt;p&gt;&lt;center&gt;&lt;em&gt;short · story · jan 2017 · web · free on &lt;a href="https://studio573games.itch.io/d-pic"&gt;itch&lt;/a&gt; · &lt;a href="https://itch.io/jam/games-made-quick-2/rate/212800"&gt;jam entry&lt;/a&gt;&lt;/em&gt;&lt;/center&gt;&lt;/p&gt;
&lt;p&gt;This is a short story about not sending dick pics.  It&amp;#8217;s &lt;em&gt;very&lt;/em&gt; short, so I can&amp;#8217;t say much without spoiling it, but: you are generally prompted to either text something reasonable, or send a dick pic.  You should not send a dick&amp;nbsp;pic.&lt;/p&gt;
&lt;p&gt;It&amp;#8217;s a fascinating artifact, not because of the work itself, but because it&amp;#8217;s so terse that I genuinely can&amp;#8217;t tell what the author was even &lt;em&gt;going&lt;/em&gt; for.  And this is the kind of subject where the author was, surely, going for &lt;em&gt;something&lt;/em&gt;.  Right?  But was it genuinely intended to be educational, or was it tongue-in-cheek about how some dudes still don&amp;#8217;t get it?  Or is it side-eying the player who clicks the obviously wrong option just for kicks, which is the same reason people do it for real?  Or is it commentary on how &amp;#8220;send a dick pic&amp;#8221; is a literal option for every response in a real conversation, too, and it&amp;#8217;s not that hard to just not do it — unless you are one of the kinds of people who just feels a &lt;em&gt;compulsion&lt;/em&gt; to try everything, anything, just because you can?  Or is it just a quick Twine and I am way too deep in this?  God, just play the thing, it&amp;#8217;s shorter than this&amp;nbsp;paragraph.&lt;/p&gt;
&lt;p&gt;I&amp;#8217;m also left wondering when it &lt;em&gt;is&lt;/em&gt; appropriate to send a dick pic.  Presumably there is a correct time?  Hopefully the author will enter Strawberry Jam 2 to expound upon&amp;nbsp;this.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;&lt;span class="caps"&gt;FINAL&lt;/span&gt; &lt;span class="caps"&gt;SCORE&lt;/span&gt;:&lt;/strong&gt; 3½”&amp;nbsp;;)&lt;/p&gt;
&lt;h2 id="marble-maze-by-shtille"&gt;&lt;a class="toclink" href="#marble-maze-by-shtille"&gt;Marble maze, by Shtille&lt;/a&gt;&lt;/h2&gt;
&lt;div class="prose-full-illustration"&gt;
&lt;img src="https://eev.ee/media/game-night/games-made-quick-2/marble-maze.jpg" alt=""&gt;
&lt;/div&gt;

&lt;p&gt;&lt;center&gt;&lt;em&gt;short · arcade · jan 2017 · win · free on &lt;a href="https://shtille.itch.io/marble-maze"&gt;itch&lt;/a&gt; · &lt;a href="https://itch.io/jam/games-made-quick-2/rate/214039"&gt;jam entry&lt;/a&gt;&lt;/em&gt;&lt;/center&gt;&lt;/p&gt;
&lt;p&gt;Ah, hm.  So this is a maze navigated by rolling a marble around.  You use &lt;span class="caps"&gt;WASD&lt;/span&gt; to move the marble, and you can also turn the camera with the arrow&amp;nbsp;keys.&lt;/p&gt;
&lt;p&gt;The trouble is…  the marble&amp;#8217;s movement is always relative to &lt;em&gt;the world&lt;/em&gt;, &lt;strong&gt;not the camera&lt;/strong&gt;.  That means if you turn the camera 30° and then try to move the marble, it&amp;#8217;ll move at a 30° angle from your point of&amp;nbsp;view.&lt;/p&gt;
&lt;p&gt;That makes navigating a maze, er,&amp;nbsp;difficult.&lt;/p&gt;
&lt;p&gt;Camera-relative movement is the kind of thing I take so much for granted that I wouldn&amp;#8217;t even think to do otherwise, and I think it&amp;#8217;s valuable to look at surprising choices that violate fundamental conventions, so I&amp;#8217;m trying to take this as a nudge out of my comfort zone.  What &lt;em&gt;could&lt;/em&gt; you design in an interesting way that used world-relative movement?  Probably not the player, but maybe something else in the world, as long as you had strong landmarks?&amp;nbsp;Hmm.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;&lt;span class="caps"&gt;FINAL&lt;/span&gt; &lt;span class="caps"&gt;SCORE&lt;/span&gt;:&lt;/strong&gt;&amp;nbsp;ᘔ&lt;/p&gt;
&lt;h2 id="refactor-flight-by-fluffy"&gt;&lt;a class="toclink" href="#refactor-flight-by-fluffy"&gt;Refactor: flight, by fluffy&lt;/a&gt;&lt;/h2&gt;
&lt;div class="prose-full-illustration"&gt;
&lt;img src="https://eev.ee/media/game-night/games-made-quick-2/refactor-flight.png" alt=""&gt;
&lt;/div&gt;

&lt;p&gt;&lt;center&gt;&lt;em&gt;short · arcade · jan 2017 · lin/mac/win · free on &lt;a href="https://fluffy.itch.io/refactor"&gt;itch&lt;/a&gt; · &lt;a href="https://itch.io/jam/games-made-quick-2/rate/155962"&gt;jam entry&lt;/a&gt;&lt;/em&gt;&lt;/center&gt;&lt;/p&gt;
&lt;p&gt;Refactor is a game album, which is rather a lot what it sounds like, and Flight is one of the tracks.  Which makes this a single, I&amp;nbsp;suppose.&lt;/p&gt;
&lt;p&gt;It&amp;#8217;s one of those games where you move down an oddly-shaped tunnel trying not to hit the walls, but with some cute twists.  Coins and gems hop up from the bottom of the screen in time with the music, and collecting them gives you points.  Hitting a wall costs you some points and kills  your momentum, but I don&amp;#8217;t think outright &lt;em&gt;losing&lt;/em&gt; is possible, which is great for&amp;nbsp;me!&lt;/p&gt;
&lt;p&gt;Also, the monk cycles through several animal faces.  I don&amp;#8217;t know why, and it&amp;#8217;s very good.  One of those odd but memorable details that sits squarely on the intersection of abstract, mysterious, and a bit weird, and refuses to budge from that&amp;nbsp;spot.&lt;/p&gt;
&lt;p&gt;The music is great too?  Really chill all&amp;nbsp;around.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;&lt;span class="caps"&gt;FINAL&lt;/span&gt; &lt;span class="caps"&gt;SCORE&lt;/span&gt;:&lt;/strong&gt;&amp;nbsp;🎵🎵🎵🎵&lt;/p&gt;
&lt;h2 id="the-adventures-of-klyde"&gt;&lt;a class="toclink" href="#the-adventures-of-klyde"&gt;The Adventures of Klyde&lt;/a&gt;&lt;/h2&gt;
&lt;div class="prose-full-illustration"&gt;
&lt;img src="https://eev.ee/media/game-night/games-made-quick-2/the-adventures-of-klyde.png" alt=""&gt;
&lt;/div&gt;

&lt;p&gt;&lt;center&gt;&lt;em&gt;short · adventure · jan 2017 · web · free on &lt;a href="https://jazdaredbone.itch.io/the-adventures-of-klyde"&gt;itch&lt;/a&gt; · &lt;a href="https://itch.io/jam/games-made-quick-2/rate/214132"&gt;jam entry&lt;/a&gt;&lt;/em&gt;&lt;/center&gt;&lt;/p&gt;
&lt;p&gt;Another bitsy game, this one starring a pig (humorously symbolized by a giant pig nose with ears) who must collect fruit and solve some&amp;nbsp;puzzles.&lt;/p&gt;
&lt;p&gt;This is charmingly nostalgic for me — it reminds me of some standard fare in engines like MegaZeux, where the obvious things to do when presented with tiles and pickups were to make mazes.  I don&amp;#8217;t mean that in a &lt;em&gt;bad&lt;/em&gt; way; the maze is the fundamental environmental&amp;nbsp;obstacle.&lt;/p&gt;
&lt;p&gt;A couple places in here felt like invisible teleport mazes I had to brute-force, but I might have been missing a hint somewhere.  I did make it through with only a little trouble, but alas — I stepped in a bad warp somewhere and got sent to the upper left corner of the starting screen, which is surrounded by walls.  So Klyde&amp;#8217;s new life is being trapped eternally in a nowhere&amp;nbsp;space.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;&lt;span class="caps"&gt;FINAL&lt;/span&gt; &lt;span class="caps"&gt;SCORE&lt;/span&gt;:&lt;/strong&gt; 19/20&amp;nbsp;apples&lt;/p&gt;
&lt;h2 id="and-more"&gt;&lt;a class="toclink" href="#and-more"&gt;And more&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;That was only a &lt;em&gt;third&lt;/em&gt; of the games, and I don&amp;#8217;t think even half of the ones I&amp;#8217;ve played.  I&amp;#8217;ll have to do a second post covering the rest of them?  Maybe a&amp;nbsp;third?&lt;/p&gt;
&lt;p&gt;Or maybe this is a ludicrous format for commenting on several dozen games and I should try to narrow it down to the ones that resonated the most for Strawberry Jam 2?  &lt;em&gt;Maybe??&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="articles"></category></entry><entry><title>Game night 2: Detention, Viatoree, Paletta</title><link href="https://eev.ee/blog/2018/01/16/game-night-2-detention-viatoree-paletta/" rel="alternate"></link><published>2018-01-16T11:04:00-08:00</published><updated>2018-01-16T11:04:00-08:00</updated><author><name>Eevee</name></author><id>tag:eev.ee,2018-01-16:/blog/2018/01/16/game-night-2-detention-viatoree-paletta/</id><summary type="html">&lt;p&gt;Game night continues with:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Detention&lt;/li&gt;
&lt;li&gt;Viatoree&lt;/li&gt;
&lt;li&gt;Paletta&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;em&gt;These are impressions, not reviews.  I try to avoid major/ending spoilers, but big plot points do tend to leave impressions.&lt;/em&gt;&lt;/p&gt;
</summary><content type="html">&lt;p&gt;Game night continues&amp;nbsp;with:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Detention&lt;/li&gt;
&lt;li&gt;Viatoree&lt;/li&gt;
&lt;li&gt;Paletta&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;em&gt;These are impressions, not reviews.  I try to avoid major/ending spoilers, but big plot points do tend to leave&amp;nbsp;impressions.&lt;/em&gt;&lt;/p&gt;


&lt;h2 id="detention"&gt;&lt;a class="toclink" href="#detention"&gt;Detention&lt;/a&gt;&lt;/h2&gt;
&lt;div class="prose-full-illustration"&gt;
&lt;img src="https://eev.ee/media/game-night/detention.jpg" alt=""&gt;
&lt;/div&gt;

&lt;p&gt;&lt;center&gt;&lt;em&gt;longish · inventory horror · jan 2017 · lin/mac/win · $12 on &lt;a href="http://store.steampowered.com/app/555220/Detention/"&gt;steam&lt;/a&gt; · &lt;a href="http://redcandlegames.com/detention/"&gt;website&lt;/a&gt;&lt;/em&gt;&lt;/center&gt;&lt;/p&gt;
&lt;p&gt;&lt;span class="dquo"&gt;&amp;#8220;&lt;/span&gt;Inventory horror&amp;#8221; is a hell of a&amp;nbsp;genre.&lt;/p&gt;
&lt;p&gt;I think this one came from a Twitter thread where glip asked for indie horror recommendations.  It&amp;#8217;s apparently well-known enough to have a &lt;a href="https://en.wikipedia.org/wiki/Detention_%28video_game%29"&gt;Wikipedia article&lt;/a&gt;, but I hadn&amp;#8217;t heard of it&amp;nbsp;before.&lt;/p&gt;
&lt;p&gt;I love love &lt;em&gt;love&lt;/em&gt; the aesthetic here.  It&amp;#8217;s obviously 2Dish from a side view (though there&amp;#8217;s plenty of parallax in a lot of places), and it&amp;#8217;s all done with…  papercraft?  I think of it as papercraft.  Everything is built out of painted chunks that look like they were cut out of paper.  It&amp;#8217;s most obvious when watching the protagonist move around; her legs and skirt swivel as she&amp;nbsp;walks.&lt;/p&gt;
&lt;p&gt;Less obvious are the occasional places where tiny details repeat in the background because a paper cutout was reused.  I don&amp;#8217;t bring that up as a dig on the art; on the contrary, I really &lt;em&gt;liked&lt;/em&gt; noticing that once or twice.  It made the world feel like it was made with a tileset (albeit with very large chunky tiles), like it&amp;#8217;s &lt;em&gt;slightly&lt;/em&gt; artificial.  I&amp;#8217;m used to seeing sidescrollers made from tiles, of course, but the tiles are usually colorful and cartoony pixel art; big gritty full-color tiles are unusual and&amp;nbsp;eerie.&lt;/p&gt;
&lt;p&gt;And that&amp;#8217;s a good thing in a horror game!  Detention&amp;#8217;s setting is already slightly unreal, and it&amp;#8217;s made all the moreso by my Western perspective: it takes place in a Taiwanese school in the 60&amp;#8217;s, a time when Taiwan was apparently under martial law.  The Steam page tells you this, but I didn&amp;#8217;t even know that much when we started playing, so I&amp;#8217;d effectively been dropped &lt;em&gt;somewhere&lt;/em&gt; on the globe and left to collect the details myself.  Even figuring out we were in Taiwan (rather than mainland China) felt like an&amp;nbsp;insight.&lt;/p&gt;
&lt;p&gt;Thinking back, it was kind of a breath of fresh air.  Games can be pretty heavy-handed about explaining the setting, but I never got that feeling from Detention.  There&amp;#8217;s more than enough context to get what&amp;#8217;s going on, but there are no &amp;#8220;stop and look at the camera while monologuing some exposition&amp;#8221; moments.  The developers &lt;em&gt;are&lt;/em&gt; based in Taiwan, so it&amp;#8217;s possible the setting is plenty familiar to them, and my perception of it is a complete accident. Either way, it certainly made an impact.  Death of the author and whatnot, I&amp;nbsp;suppose.&lt;/p&gt;
&lt;p&gt;One thing in particular that stood out: none of the Chinese text in the environment is &lt;em&gt;directly&lt;/em&gt; translated.  The protagonist&amp;#8217;s thoughts still give away what it says — &amp;#8220;this is the nurse&amp;#8217;s office&amp;#8221; and the like — but that struck me as pretty different from simply &lt;em&gt;repeating&lt;/em&gt; the text in English as though I were reading a sign in an &lt;span class="caps"&gt;RPG&lt;/span&gt;.  The text is &lt;em&gt;there&lt;/em&gt;, perfectly legible, but &lt;em&gt;I&lt;/em&gt; can&amp;#8217;t read it; I can only ask the protagonist to read it and offer her thoughts.  It drives home that I&amp;#8217;m experiencing the world through the eyes of the protagonist, who is their own person with their own impression of everything.  Again, this is largely an emergent property of the game&amp;#8217;s being designed in a culture that is not mine, but I&amp;#8217;m left wondering how much thought went into this style of&amp;nbsp;localization.&lt;/p&gt;
&lt;p&gt;The game itself sees you wandering through a dark and twisted version of the protagonist&amp;#8217;s school, collecting items and solving puzzles with them.  There&amp;#8217;s no direct combat, though some places feature a couple varieties of spirits called &lt;em&gt;lingered&lt;/em&gt; which you have to carefully avoid.  As the game progresses, the world starts to break down, alternating between increasingly abstract and increasingly &lt;em&gt;concrete&lt;/em&gt; as we find out who the protagonist is and why she&amp;#8217;s&amp;nbsp;here.&lt;/p&gt;
&lt;p&gt;The payoff is very personal and left a lasting impression…  though as I look at the Wikipedia page now, it looks like the ending we got was the &lt;em&gt;non-canon bad ending&lt;/em&gt;?!   Well, hell.  The bad ending is still great,&amp;nbsp;then.&lt;/p&gt;
&lt;p&gt;The whole game has a huge Silent Hill vibe, only without the combat and fog.  Frankly, the genre might work &lt;em&gt;better&lt;/em&gt; without combat; personal demons are more intimidating and meaningful when you can&amp;#8217;t literally shoot them with a gun until they&amp;#8217;re&amp;nbsp;dead.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;&lt;span class="caps"&gt;FINAL&lt;/span&gt; &lt;span class="caps"&gt;SCORE&lt;/span&gt;:&lt;/strong&gt;&amp;nbsp;拾&lt;/p&gt;
&lt;h2 id="viatoree"&gt;&lt;a class="toclink" href="#viatoree"&gt;Viatoree&lt;/a&gt;&lt;/h2&gt;
&lt;div class="prose-full-illustration"&gt;
&lt;img src="https://eev.ee/media/game-night/viatoree.png" alt=""&gt;
&lt;/div&gt;

&lt;p&gt;&lt;center&gt;&lt;em&gt;short · platformer · sep 2013 · win · free on &lt;a href="https://karolbelina.itch.io/viatoree"&gt;itch&lt;/a&gt;&lt;/em&gt;&lt;/center&gt;&lt;/p&gt;
&lt;p&gt;I found this because &lt;a href="https://twitter.com/itchio/status/936417403575005184"&gt;@itchio tweeted about it&lt;/a&gt;, and the phrase &amp;#8220;atmospheric platform exploration game&amp;#8221; is the second most beautiful sequence of words in the English&amp;nbsp;language.&lt;/p&gt;
&lt;p&gt;The first paragraph on the itch.io page tells you the setup.  That paragraph also contains more text than the entire game.  In short: there are five things, and you need to find them.  You can walk, jump, and extend your arms straight up to lift yourself to the ceiling.  That&amp;#8217;s it.  No enemies, no shooting, no NPCs (more or&amp;nbsp;less).&lt;/p&gt;
&lt;p&gt;The result is, indeed, an atmospheric platform exploration game.  The foreground is &lt;em&gt;entirely&lt;/em&gt; 1-bit pixel art, save for the occasional white pixel to indicate someone&amp;#8217;s eyes, and the background is only a few shades of the same purple hue.  The game becomes less about playing and more about just looking at the environmental detail, appreciating how much texture the game manages to squeeze out of chunky colorless pixels.  The world is still &lt;em&gt;alive&lt;/em&gt;, too, much moreso than most platformers; tiny critters appear here and there, doing some wandering of their own, completely oblivious to&amp;nbsp;you.&lt;/p&gt;
&lt;p&gt;The game is really short, but it…  just…  makes me happy.  I&amp;#8217;m happy that this can &lt;em&gt;exist&lt;/em&gt;, that not only is it okay for someone to make a very compact and short game, but that the result can still resonate with me.  Not everything needs to be a sprawling epic or ask me to dedicate hours of time.  It takes a few tiny ideas, runs with them, does what it came to do, and ends there.  I love games like&amp;nbsp;this.&lt;/p&gt;
&lt;p&gt;That sounds silly to write out, but it&amp;#8217;s been hard to get into my head!  I do like experimenting, but I also feel compelled to reach for the grandiose, and &lt;em&gt;grandiose experiment&lt;/em&gt; sounds more like mad science than creative exploration.  For whatever reason, Viatoree convinced me that it&amp;#8217;s okay to do a small thing, in a way that no other jam game has.  It was probably the catalyst that led me to make &lt;a href="https://eevee.itch.io/roguelike-simulator"&gt;Roguelike Simulator&lt;/a&gt;, and I thank it for&amp;nbsp;that.&lt;/p&gt;
&lt;p&gt;Unfortunately, we collected four of the five macguffins before hitting upon on a puzzle we couldn&amp;#8217;t make heads or tails of.  After about ten minutes of fruitless searching, I decided to abandon this one unfinished, rather than bore my couch partner to tears.  Maybe I&amp;#8217;ll go take another stab at it after I post&amp;nbsp;this.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;&lt;span class="caps"&gt;FINAL&lt;/span&gt; &lt;span class="caps"&gt;SCORE&lt;/span&gt;:&lt;/strong&gt;&amp;nbsp;●●●●○&lt;/p&gt;
&lt;h2 id="paletta"&gt;&lt;a class="toclink" href="#paletta"&gt;Paletta&lt;/a&gt;&lt;/h2&gt;
&lt;div class="prose-full-illustration"&gt;
&lt;img src="https://eev.ee/media/game-night/paletta.png" alt=""&gt;
&lt;/div&gt;

&lt;p&gt;&lt;center&gt;&lt;em&gt;medium · puzzle story · nov 2017 · win · free on &lt;a href="https://cosmic-latte-games.itch.io/paletta"&gt;itch&lt;/a&gt;&lt;/em&gt;&lt;/center&gt;&lt;/p&gt;
&lt;p&gt;Paletta, another &lt;span class="caps"&gt;RPG&lt;/span&gt; Maker work, won second place in the month-long &lt;a href="https://itch.io/jam/igmc2017"&gt;Indie Game Maker Contest 2017&lt;/a&gt;.  Nice!  Apparently &lt;span class="caps"&gt;MOOP&lt;/span&gt; came in fourth in the same jam; also nice!  I guess that&amp;#8217;s why both of them ended up on the itch front&amp;nbsp;page.&lt;/p&gt;
&lt;p&gt;The game is set in a world drained of color, and you have to go restore it.  Each land contains one lost color, and each color gives you a corresponding spell, which is generally used for some light puzzle-solving in further lands.  It&amp;#8217;s a very cute and light-hearted game, and it actually does an impressive job of obscuring its &lt;span class="caps"&gt;RPG&lt;/span&gt; Maker&amp;nbsp;roots.&lt;/p&gt;
&lt;p&gt;The world feels a little small to me, despite having fairly spacious maps.  The progression is pretty linear: you enter one land, talk to a small handful of NPCs, solve the one puzzle, get the color, and move on.  I think all the areas were continuously connected, too, which may have thrown me off a bit — these areas are described as though they were vast regions, but they&amp;#8217;re all a hundred feet wide and nestled right next to each&amp;nbsp;other.&lt;/p&gt;
&lt;p&gt;I love playing with color as a concept, and I wish the game had run further with it somehow.  Rescuing a color does add some color back to the world, but at times it seemed like the color that reappeared was somewhat arbitrary?  It&amp;#8217;s not like you rescue green and now all the green is back.  Thinking back on it now, I wonder if each rescued color actually changed a fixed set of &lt;em&gt;sprites&lt;/em&gt; from gray to colorized?  But it&amp;#8217;s been a month (oops) and now I&amp;#8217;m not&amp;nbsp;sure.&lt;/p&gt;
&lt;p&gt;I&amp;#8217;m not trying to pick on the authors for the brevity of their &lt;em&gt;jam game&lt;/em&gt; and also first game they&amp;#8217;ve ever finished.  I enjoyed playing it and found it plenty charming!  It just happens that this time, what left the biggest impression on me was a nebulous feeling that something was missing.  I think that&amp;#8217;s still plenty important to&amp;nbsp;ponder.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;&lt;span class="caps"&gt;FINAL&lt;/span&gt; &lt;span class="caps"&gt;SCORE&lt;/span&gt;:&lt;/strong&gt;&amp;nbsp;❤️💛💚💙💜&lt;/p&gt;</content><category term="articles"></category></entry><entry><title>Physics cheats</title><link href="https://eev.ee/blog/2018/01/06/physics-cheats/" rel="alternate"></link><published>2018-01-06T22:08:00-08:00</published><updated>2018-01-06T22:08:00-08:00</updated><author><name>Eevee</name></author><id>tag:eev.ee,2018-01-06:/blog/2018/01/06/physics-cheats/</id><summary type="html">&lt;p&gt;Anonymous asks:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;something about how we tweak physics to “work” better in games?&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Ho ho!  &lt;em&gt;Work&lt;/em&gt;.  Get it?  Like in physics…?&lt;/p&gt;
</summary><content type="html">&lt;p&gt;Anonymous&amp;nbsp;asks:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;something about how we tweak physics to &amp;#8220;work&amp;#8221; better in&amp;nbsp;games?&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Ho ho!  &lt;em&gt;Work&lt;/em&gt;.  Get it?  Like in&amp;nbsp;physics…?&lt;/p&gt;


&lt;h2 id="hitboxes"&gt;&lt;a class="toclink" href="#hitboxes"&gt;Hitboxes&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;&lt;span class="dquo"&gt;&amp;#8220;&lt;/span&gt;Hitbox&amp;#8221; is perhaps not the most accurate term, since the shape used for colliding with the environment and the shape used for detecting damage might be totally different.  They&amp;#8217;re usually the same in simple platformers, though, and that&amp;#8217;s what most of my games have&amp;nbsp;been.&lt;/p&gt;
&lt;p&gt;The hitbox is the biggest physics fudge by far, and it exists because of a single massive approximation that (&lt;a href="http://www.foddy.net/Athletics.html"&gt;most&lt;/a&gt;) games make: you&amp;#8217;re controlling a single entity in the abstract, not a physical body in great&amp;nbsp;detail.&lt;/p&gt;
&lt;p&gt;That is: when you walk with your real-world meat shell, you perform a complex dance of putting one foot in front of the other, a motion you spent &lt;em&gt;years&lt;/em&gt; perfecting.  When you walk in a video game, you press a single &amp;#8220;walk&amp;#8221; button.  Your avatar may play an animation that moves its legs back and forth, but since you&amp;#8217;re not actually controlling the legs independently (and since simulating them is way harder), the game just treats you like a simple shape.  Fairly often, this is a box, or something very&amp;nbsp;box-like.&lt;/p&gt;
&lt;div class="prose-full-illustration"&gt;
&lt;img src="https://eev.ee/media/2018-01-06-physics/1-hitbox.png" alt="An Eevee sprite standing on faux ground; the size of the underlying image and the hitbox are outlined"&gt;
&lt;/div&gt;

&lt;p&gt;Since the player has no direct control over the exact placement of their limbs, it would be slightly frustrating to have them collide with the world.  This is especially true in cases like the above, where the tail and left ear protrude significantly out from the main body.  If that Eevee wanted to stand against a real-world wall, she would simply tilt her ear or tail out of the way, so there&amp;#8217;s no reason for the ear to block her from standing against a game wall.  To compensate for this, the ear and tail are left out of the collision box entirely and will simply jut into a wall if necessary — a goofy affordance that&amp;#8217;s so common it doesn&amp;#8217;t even register as unusual.  As a bonus (assuming this same box is used for combat), she won&amp;#8217;t take damage from projectiles that merely graze past an&amp;nbsp;ear.&lt;/p&gt;
&lt;p&gt;(One extra consideration for sprite games in particular: the hitbox ought to be horizontally symmetric around the sprite&amp;#8217;s pivot — i.e. the point where the entity is truly considered to be standing — so that the hitbox doesn&amp;#8217;t abruptly move when the entity turns&amp;nbsp;around!)&lt;/p&gt;
&lt;h3 id="corners"&gt;&lt;a class="toclink" href="#corners"&gt;Corners&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Treating the player (and indeed most objects) as a box has one annoying side effect: boxes have &lt;em&gt;corners&lt;/em&gt;.  Corners can catch on other corners, even by a single pixel.  Real-world bodies tend to be a bit rounder and squishier and this can tolerate grazing a corner; even real-world boxes will simply rotate a&amp;nbsp;bit.&lt;/p&gt;
&lt;p&gt;Ah, but in our faux physics world, we generally don&amp;#8217;t want conscious actors (such as the player) to rotate, even with a realistic physics simulator!  Real-world bodies are made of parts that will generally try to keep you upright, after all; you don&amp;#8217;t tilt back and forth&amp;nbsp;much.&lt;/p&gt;
&lt;p&gt;One way to handle corners is to simply remove them from conscious actors.  A hitbox doesn&amp;#8217;t &lt;em&gt;have&lt;/em&gt; to be a literal box, after all.  A popular alternative — especially in Unity where it&amp;#8217;s a standard asset — is the pill-shaped &lt;em&gt;capsule&lt;/em&gt;, which has semicircles/hemispheres on the top and bottom and a cylindrical body in 3D.  No corners, no&amp;nbsp;problem.&lt;/p&gt;
&lt;p&gt;Of course, that introduces a new problem: now the player can&amp;#8217;t balance precariously on edges without their rounded bottom sliding them off.&amp;nbsp;Alas.&lt;/p&gt;
&lt;p&gt;If you&amp;#8217;re stuck with corners, then, you may want to use a &lt;em&gt;corner bump&lt;/em&gt;, a term I just made up.  If the player &lt;em&gt;would&lt;/em&gt; collide with a corner, but the collision is only by a few pixels, just nudge them to the side a bit and carry&amp;nbsp;on.&lt;/p&gt;
&lt;div class="prose-full-illustration"&gt;
&lt;img src="https://eev.ee/media/2018-01-06-physics/2-corners.png" alt="An Eevee sprite trying to move sideways into a shallow ledge; the game bumps her upwards slightly, so she steps onto it instead"&gt;
&lt;/div&gt;

&lt;p&gt;When the corner is horizontal, this creates stairs!  This is, more or less kinda, how steps work in Doom: when the player tries to cross from one sector into another, if the height difference is 24 units or less, the game simply bumps them upwards to the height of the new floor and lets them continue&amp;nbsp;on.&lt;/p&gt;
&lt;p&gt;Implementing this in a game &lt;em&gt;without&lt;/em&gt; Doom&amp;#8217;s notion of sectors is a little trickier.  In fact, I still haven&amp;#8217;t done it.  Collision detection based on &lt;em&gt;rejection&lt;/em&gt; gets it for free, &lt;em&gt;kinda&lt;/em&gt;, but it&amp;#8217;s not very deterministic and it breaks other things.  But that&amp;#8217;s a whole other&amp;nbsp;post.&lt;/p&gt;
&lt;h2 id="gravity"&gt;&lt;a class="toclink" href="#gravity"&gt;Gravity&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Gravity is &lt;em&gt;pretty&lt;/em&gt; easy.  Everything accelerates downwards all the time.  What&amp;#8217;s interesting are the&amp;nbsp;exceptions.&lt;/p&gt;
&lt;h3 id="jumping"&gt;&lt;a class="toclink" href="#jumping"&gt;Jumping&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Jumping is a giant&amp;nbsp;hack.&lt;/p&gt;
&lt;p&gt;Think about how actual jumping works: you &lt;em&gt;tense your legs&lt;/em&gt;, which generally involves bending your knees first, and then spring upwards.  In a platformer, you can just leap whenever you feel like it, which is nonsense.  Also you go like twenty feet into the&amp;nbsp;air?&lt;/p&gt;
&lt;p&gt;Worse, most platformers allow &lt;em&gt;variable-height&lt;/em&gt; jumping, where your jump is lower if you let go of the jump button &lt;em&gt;while you&amp;#8217;re in the air&lt;/em&gt;.  Normally, one would expect to have to decide how much force to put into the jump&amp;nbsp;beforehand.&lt;/p&gt;
&lt;p&gt;But of course this is about convenience of controls: when jumping is your primary action, you want to be able to do it &lt;em&gt;immediately&lt;/em&gt;, without any windup for how high you want to&amp;nbsp;jump.&lt;/p&gt;
&lt;p&gt;(And then there&amp;#8217;s double jumping?  Come &lt;em&gt;on&lt;/em&gt;.)&lt;/p&gt;
&lt;p&gt;Air control is a similar phenomenon: usually you&amp;#8217;d jump in a particular direction by controlling how you push off the ground with your feet, but in a video game, you don&amp;#8217;t have feet!  You only have the box.  The compromise is to let you control your horizontal movement to a limited degree in midair, even though that doesn&amp;#8217;t make any sense.  (It&amp;#8217;s way more fun, though, and overall gives you more movement options, which are good to have in an interactive&amp;nbsp;medium.)&lt;/p&gt;
&lt;p&gt;Air control also exposes an obvious place that game physics collide with the realistic model of serious physics engines.  I&amp;#8217;ve mentioned this before, but: if you use Real Physics™ and air control yourself into a wall, you might find that you&amp;#8217;ll simply &lt;em&gt;stick to the wall&lt;/em&gt; until you let go of the movement buttons.  Why?  Remember, player movement acts as though an external force were pushing you around (and from the perspective of a Real™ physics engine, this is exactly how you&amp;#8217;d implement it) — so air-controlling into a wall is equivalent to pushing a book against a wall with your hand, and the friction with the wall holds you in place.&amp;nbsp;Oops.&lt;/p&gt;
&lt;h3 id="ground-sticking"&gt;&lt;a class="toclink" href="#ground-sticking"&gt;Ground sticking&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Another place game physics conflict with physics engines is with running to the top of a slope.  On a real hill, of course, you land on top of the slope and are probably glad of it; slopes are hard to&amp;nbsp;climb!&lt;/p&gt;
&lt;div class="prose-full-illustration"&gt;
&lt;img src="https://eev.ee/media/2018-01-06-physics/3-ground-sticking.png" alt="An Eevee moves to the top of a slope, and rather than step onto the flat top, she goes flying off into the air"&gt;
&lt;/div&gt;

&lt;p&gt;In a video game, you go flying.  Because you&amp;#8217;re a box.  With momentum.  So you hit the peak and keep going in the same direction.  Which is diagonally&amp;nbsp;upwards.&lt;/p&gt;
&lt;h3 id="projectiles"&gt;&lt;a class="toclink" href="#projectiles"&gt;Projectiles&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;To make them more predictable, projectiles generally aren&amp;#8217;t subject to gravity, at least as far as I&amp;#8217;ve seen.  The real world does not have such an exemption.  The real world imposes gravity even on &lt;em&gt;sniper rifles&lt;/em&gt;, which in a video game are often implemented as an &lt;em&gt;instant&lt;/em&gt; trace unaffected by anything in the world because the bullet never actually exists &lt;em&gt;in&lt;/em&gt; the&amp;nbsp;world.&lt;/p&gt;
&lt;h2 id="resistance"&gt;&lt;a class="toclink" href="#resistance"&gt;Resistance&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Ah.  Welcome to&amp;nbsp;hell.&lt;/p&gt;
&lt;h3 id="water"&gt;&lt;a class="toclink" href="#water"&gt;Water&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Water is an interesting case, and offhand I don&amp;#8217;t know the gritty details of how games implement it.  In the real world, water applies a resistant &lt;a href="https://en.wikipedia.org/wiki/Drag_equation"&gt;drag force&lt;/a&gt; to movement — and that force is proportional to the &lt;em&gt;square&lt;/em&gt; of velocity, which I&amp;#8217;d completely forgotten until right now.  I am almost positive that no game handles that correctly.  But then, in real-world water, you can push against the water itself for movement, and games don&amp;#8217;t simulate that either.  What&amp;#8217;s the rough&amp;nbsp;equivalent?&lt;/p&gt;
&lt;p&gt;The &lt;a href="http://info.sonicretro.org/SPG:Underwater"&gt;Sonic Physics Guide&lt;/a&gt; suggests that Sonic handles it by basically halving everything: acceleration, max speed, friction, etc.  When Sonic enters water, his speed is cut; when Sonic exits water, his speed is&amp;nbsp;increased.&lt;/p&gt;
&lt;p&gt;That last bit feels validating — I could swear Metroid Prime did the same thing, and built my own solution around it, but couldn&amp;#8217;t remember for sure.  It makes no sense, of course, for a jump to &lt;em&gt;become faster&lt;/em&gt; just because you happened to break the surface of the water, but it feels &lt;em&gt;fantastic&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;The thing I did was similar, except that I didn&amp;#8217;t want to add a multiplier in a dozen places when you happen to be underwater (and remember which ones need it to be squared, etc.).  So instead, I calculate everything completely as normal, so velocity is exactly the same as it would be on dry land — but the &lt;em&gt;distance you would move&lt;/em&gt; gets halved.  The effect seems to be pretty similar to most platformers with water, at least as far as I can tell.  It hasn&amp;#8217;t shown up in a published game and I only added this fairly recently, so I might be overlooking some reason this is a bad&amp;nbsp;idea.&lt;/p&gt;
&lt;p&gt;(One reason that comes to mind is that velocity is now a little white lie while underwater, so anything relying on velocity for interesting effects might be thrown off.  Or maybe that&amp;#8217;s correct, because velocity thresholds should be halved underwater too?&amp;nbsp;Hm!)&lt;/p&gt;
&lt;p&gt;Notably, &lt;em&gt;air&lt;/em&gt; is also a fluid, so it should behave the same way (just with different constants).  I &lt;em&gt;definitely&lt;/em&gt; don&amp;#8217;t think any games apply air drag that&amp;#8217;s proportional to the square of&amp;nbsp;velocity.&lt;/p&gt;
&lt;h3 id="friction"&gt;&lt;a class="toclink" href="#friction"&gt;Friction&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Friction is, in my experience, a &lt;em&gt;little&lt;/em&gt; handwaved.  Probably because real-world friction is so darn &lt;a href="https://en.wikipedia.org/wiki/Friction"&gt;complicated&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Consider that in the real world, we want very &lt;em&gt;high&lt;/em&gt; friction on the surfaces we walk on — shoes and tires are explicitly designed to increase it, even.  We move by bracing a back foot against the ground and using that to push ourselves forward, so we want the ground to resist our push as much as&amp;nbsp;possible.&lt;/p&gt;
&lt;p&gt;In a game world, we are a box.  We move by being pushed by some invisible &lt;em&gt;outside&lt;/em&gt; force, so if the friction between ourselves and the ground is too high, we won&amp;#8217;t be able to move at all!  That&amp;#8217;s complete nonsense physically, but it turns out to be handy in some cases — for example, highish friction can simulate walking through deep mud, which &lt;em&gt;should&lt;/em&gt; be difficult due to fluid drag and &lt;em&gt;low&lt;/em&gt;&amp;nbsp;friction.&lt;/p&gt;
&lt;p&gt;But the best-known example of the fakeness of game friction is &lt;em&gt;video game ice&lt;/em&gt;.  Walking on real-world ice is difficult because the low friction means low grip; your feet are likely to slip out from under you, and you&amp;#8217;ll simply fall down and have trouble moving at all.  In a video game, you &lt;em&gt;can&amp;#8217;t&lt;/em&gt; fall down, so you have the opposite experience: you spend most of your time sliding around uncontrollably.  Yet ice is so common in video games (and perhaps so uncommon in places I&amp;#8217;ve lived) that I, at least, had never really thought about this disparity until an hour or so&amp;nbsp;ago.&lt;/p&gt;
&lt;h3 id="game-friction-vs-real-world-friction"&gt;&lt;a class="toclink" href="#game-friction-vs-real-world-friction"&gt;Game friction vs real-world friction&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Real-world friction is a &lt;em&gt;force&lt;/em&gt;.  It&amp;#8217;s the &lt;em&gt;normal force&lt;/em&gt; (which is the force exerted by the object on the surface) times some constant that depends on how the two materials&amp;nbsp;interact.&lt;/p&gt;
&lt;p&gt;Force is mass times acceleration, and platformers often ignore mass, so friction ought to be an acceleration — applied against the object&amp;#8217;s movement, but never enough to push it&amp;nbsp;backwards.&lt;/p&gt;
&lt;p&gt;I haven&amp;#8217;t made any games where variable friction plays a significant role, but my gut instinct is that low friction should mean the player accelerates more slowly but has a higher max speed, and high friction should mean the opposite.  I see from my own source code that I didn&amp;#8217;t even do what I just said, so let&amp;#8217;s defer to some better-made and well-documented games: &lt;a href="http://info.sonicretro.org/SPG:Running#Friction"&gt;Sonic&lt;/a&gt; and &lt;a href="https://zdoom.org/wiki/Friction"&gt;Doom&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;In Sonic, friction is a fixed value subtracted from the player&amp;#8217;s velocity (regardless of direction) each tic.  Sonic has a fixed framerate, so the units are really pixels per tic squared (i.e. acceleration), multiplied by an implicit 1 tic per tic.  So far, so&amp;nbsp;good.&lt;/p&gt;
&lt;p&gt;But Sonic&amp;#8217;s friction only applies if the player isn&amp;#8217;t pressing &lt;kbd&gt;←&lt;/kbd&gt; or &lt;kbd&gt;→&lt;/kbd&gt;.  Hang on, that isn&amp;#8217;t friction at all; that&amp;#8217;s just deceleration!  That&amp;#8217;s equivalent to jogging to a stop.  If friction were lower, Sonic would take longer to stop, but otherwise this is only tangentially related to&amp;nbsp;friction.&lt;/p&gt;
&lt;p&gt;(In fairness, this approach &lt;em&gt;would&lt;/em&gt; decently emulate friction for non-conscious sliding objects, which are never going to be pressing movement buttons.  Also, we don&amp;#8217;t have the Sonic source code, and the name &amp;#8220;friction&amp;#8221; is a fan invention; the Sonic Physics Guide already uses &amp;#8220;deceleration&amp;#8221; to describe the player&amp;#8217;s acceleration when &lt;em&gt;turning around&lt;/em&gt;.)&lt;/p&gt;
&lt;p&gt;Okay, let&amp;#8217;s try Doom.  In Doom, the default friction is&amp;nbsp;90.625%.&lt;/p&gt;
&lt;p&gt;Hang on,&amp;nbsp;what?&lt;/p&gt;
&lt;p&gt;Yes, in Doom, friction is a &lt;em&gt;multiplier&lt;/em&gt; applied every tic.  Doom runs at 35 tics per second, so this is a multiplier of 0.032 &lt;em&gt;per second&lt;/em&gt;.&amp;nbsp;Yikes!&lt;/p&gt;
&lt;p&gt;This isn&amp;#8217;t anything remotely like real friction, but it&amp;#8217;s much easier to implement.  With friction as acceleration, the game has to know both the direction of movement (so it can apply friction in the opposite direction) and the magnitude (so it doesn&amp;#8217;t overshoot and launch the object in the other direction).  That means taking a semi-costly square root and also writing extra code to cap the amount of friction.  With a multiplier, neither is necessary; just multiply the whole velocity vector and you&amp;#8217;re&amp;nbsp;done.&lt;/p&gt;
&lt;p&gt;There are some downsides.  One is that objects will never actually &lt;em&gt;stop&lt;/em&gt;, since multiplying by 3% repeatedly will never produce a result of zero — though eventually the speed will become small enough to either slip below a &amp;#8220;minimum speed&amp;#8221; threshold or simply no longer fit in a float representation.  Another is that the units are fairly meaningless: with Doom&amp;#8217;s default friction of 90.625%, about how long does it take for the player to stop?  I have no idea, partly because &amp;#8220;stop&amp;#8221; is ambiguous here!  If friction were an acceleration, I could divide it into the player&amp;#8217;s max speed to get a&amp;nbsp;time.&lt;/p&gt;
&lt;p&gt;All that aside, what are the actual effects of changing Doom&amp;#8217;s friction?  What an excellent question that&amp;#8217;s surprisingly tricky to answer.  (Note that friction can&amp;#8217;t be changed in original Doom, only in the Boom port and its derivatives.)  Here&amp;#8217;s what I&amp;#8217;ve pieced&amp;nbsp;together.&lt;/p&gt;
&lt;p&gt;Doom&amp;#8217;s &amp;#8220;friction&amp;#8221; is really two values.  &amp;#8220;Friction&amp;#8221; itself is a multiplier applied to moving objects on every tic, but there&amp;#8217;s also a &lt;em&gt;move factor&lt;/em&gt; which defaults&amp;nbsp;to &lt;span class="math"&gt;\(\frac{1}{32} = 0.03125\)&lt;/span&gt; and is derived from friction for custom&amp;nbsp;values.&lt;/p&gt;
&lt;p&gt;Every tic, the player&amp;#8217;s velocity is multiplied by friction, and then increased by their speed times the move&amp;nbsp;factor.&lt;/p&gt;
&lt;div class="math"&gt;$$
v(n) = v(n - 1) \times friction + speed \times move factor
$$&lt;/div&gt;
&lt;p&gt;Eventually, the reduction from friction will balance out the speed boost.  That happens&amp;nbsp;when &lt;span class="math"&gt;\(v(n) = v(n - 1)\)&lt;/span&gt;, so we can rearrange it to find the player&amp;#8217;s effective max&amp;nbsp;speed:&lt;/p&gt;
&lt;div class="math"&gt;$$
v = v \times friction + speed \times move factor \\
v - v \times friction = speed \times move factor \\
v = speed \times \frac{move factor}{1 - friction}
$$&lt;/div&gt;
&lt;p&gt;For vanilla Doom&amp;#8217;s move factor of 0.03125 and friction of 0.90625, that&amp;nbsp;becomes:&lt;/p&gt;
&lt;div class="math"&gt;$$
v = speed \times \frac{\frac{1}{32}}{1 - \frac{29}{32}} = speed \times \frac{\frac{1}{32}}{\frac{3}{32}} = \frac{1}{3} \times speed
$$&lt;/div&gt;
&lt;p&gt;Curiously, &amp;#8220;speed&amp;#8221; is three times the maximum speed an actor can actually move.  Doomguy&amp;#8217;s run speed is 50, so in practice he moves a third of that, or 16⅔ units per tic.  (Of course, this isn&amp;#8217;t counting &lt;span class="caps"&gt;SR40&lt;/span&gt;, a bug that lets Doomguy run ~40% faster than intended&amp;nbsp;diagonally.)&lt;/p&gt;
&lt;p&gt;So now, what if you change friction?  Even more curiously, the move factor is calculated completely differently depending on whether friction is higher or lower than the default Doom&amp;nbsp;amount:&lt;/p&gt;
&lt;div class="math"&gt;$$
move factor = \begin{cases}
\frac{133 - 128 \times friction}{544} &amp;amp;≈ 0.244 - 0.235 \times friction &amp;amp; \text{ if } friction \ge \frac{29}{32} \\
\frac{81920 \times friction - 70145}{1048576} &amp;amp;≈ 0.078 \times friction - 0.067 &amp;amp; \text{ otherwise }
\end{cases}
$$&lt;/div&gt;
&lt;p&gt;That&amp;#8217;s pretty weird?  Complicating things further is that low friction (which means muddy terrain, remember) has an extra multiplier on its move factor, depending on how fast you&amp;#8217;re already going — the idea is apparently that you have a hard time getting going, but it gets easier as you find your footing.  The extra multiplier maxes out at 8, which makes the two halves of that function meet at the vanilla Doom&amp;nbsp;value.&lt;/p&gt;
&lt;div class="prose-full-illustration"&gt;
&lt;img src="https://eev.ee/media/2018-01-06-physics/boom-move-factor.png" alt="A graph of the relationship between friction and move factor"&gt;
&lt;/div&gt;

&lt;p&gt;That very top point corresponds to the move factor from the original game.  So no matter what you do to friction, the move factor becomes &lt;em&gt;lower&lt;/em&gt;.  At 0.85 and change, you can no longer move at all; below that, you move &lt;em&gt;backwards&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;From the formula above, it&amp;#8217;s easy to see what changes to friction and move factor will do to Doomguy&amp;#8217;s stable velocity.  Move factor is in the numerator, so increasing it will increase stable velocity — but it can&amp;#8217;t increase, so stable velocity can only ever decrease.  Friction is in the denominator, but it&amp;#8217;s subtracted from 1, so increasing friction will make the denominator a smaller value less than 1, i.e. increase stable velocity.  Combined, we get this relationship between friction and stable&amp;nbsp;velocity.&lt;/p&gt;
&lt;div class="prose-full-illustration"&gt;
&lt;img src="https://eev.ee/media/2018-01-06-physics/boom-stable-velocity.png" alt="A graph showing stable velocity shooting up dramatically as friction increases"&gt;
&lt;/div&gt;

&lt;p&gt;As friction approaches 1, stable velocity grows without bound.  This makes sense, given the definition&amp;nbsp;of &lt;span class="math"&gt;\(v(n)\)&lt;/span&gt; — if friction is 1, the velocity from the previous tic isn&amp;#8217;t reduced at all, so we just keep accelerating&amp;nbsp;freely.&lt;/p&gt;
&lt;p&gt;All of this is why I&amp;#8217;m wary of using&amp;nbsp;multipliers.&lt;/p&gt;
&lt;p&gt;Anyway, this leaves me with one last question about the effects of Doom&amp;#8217;s friction: &lt;em&gt;how long&lt;/em&gt; does it take to reach stable velocity?  Barring precision errors, we&amp;#8217;ll never truly &lt;em&gt;reach&lt;/em&gt; stable velocity, but let&amp;#8217;s say within 5%.  First we need a closed formula for the velocity after some number of tics.  This is a simple recurrence relation, and you can write a few terms out yourself if you want to be sure this is&amp;nbsp;right.&lt;/p&gt;
&lt;div class="math"&gt;$$
v(n) = v_0 \times friction^n + speed \times move factor \times \frac{friction^n - 1}{friction - 1}
$$&lt;/div&gt;
&lt;p&gt;Our initial velocity is zero, so the first term disappears.  Set this equal to the stable formula and solve for&amp;nbsp;n:&lt;/p&gt;
&lt;div class="math"&gt;$$
speed \times move factor \times \frac{friction^n - 1}{friction - 1} = (1 - 5\%) \times speed \times \frac{move factor}{1 - friction} \\
friction^n - 1 = -(1 - 5\%) \\
n = \frac{\ln 5\%}{\ln friction}
$$&lt;/div&gt;
&lt;p&gt;&lt;span class="dquo"&gt;&amp;#8220;&lt;/span&gt;Speed&amp;#8221; and move factor disappear entirely, which makes sense, and this is purely a function of friction (and how close we want to get).  For vanilla Doom, that comes out to 30.4, which is a little less than a second.  For other values of&amp;nbsp;friction:&lt;/p&gt;
&lt;div class="prose-full-illustration"&gt;
&lt;img src="https://eev.ee/media/2018-01-06-physics/boom-stable-time.png" alt="A graph of time to stability which leaps upwards dramatically towards the right"&gt;
&lt;/div&gt;

&lt;p&gt;As friction increases (which in Doom terms means the surface is more slippery), it takes longer and longer to reach stable speed, which is in turn greater and greater.  For lesser friction (i.e. mud), stable speed is lower, but reached fairly quickly.  (Of course, the extra &amp;#8220;getting going&amp;#8221; multiplier while in mud adds some extra time here, but including that in the graph is a bit more&amp;nbsp;complicated.)&lt;/p&gt;
&lt;p&gt;I &lt;em&gt;think&lt;/em&gt; this matches with my instincts above.  How&amp;nbsp;fascinating!&lt;/p&gt;
&lt;p&gt;What&amp;#8217;s that?  This is way too much math and you hate it?  Then &lt;strong&gt;don&amp;#8217;t use multipliers in game&amp;nbsp;physics.&lt;/strong&gt;&lt;/p&gt;
&lt;h2 id="uh"&gt;&lt;a class="toclink" href="#uh"&gt;Uh&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;That was a hell of a&amp;nbsp;diversion!&lt;/p&gt;
&lt;p&gt;I guess the goofiest stuff in basic game physics is really just about mapping player controls to in-game actions like jumping and deceleration; the rest consists of hacks to compensate for representing everything as a&amp;nbsp;box.&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="articles"></category><category term="tech"></category><category term="patreon"></category><category term="math"></category><category term="gamedev"></category><category term="doom"></category></entry><entry><title>Random with care</title><link href="https://eev.ee/blog/2018/01/02/random-with-care/" rel="alternate"></link><published>2018-01-02T17:34:00-08:00</published><updated>2018-01-02T17:34:00-08:00</updated><author><name>Eevee</name></author><id>tag:eev.ee,2018-01-02:/blog/2018/01/02/random-with-care/</id><summary type="html">&lt;p&gt;Hi!  Here are a few loose thoughts about picking random numbers.&lt;/p&gt;
</summary><content type="html">&lt;p&gt;Hi!  Here are a few loose thoughts about picking random&amp;nbsp;numbers.&lt;/p&gt;


&lt;h2 id="a-word-about-crypto"&gt;&lt;a class="toclink" href="#a-word-about-crypto"&gt;A word about crypto&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;&lt;span class="caps"&gt;DON&lt;/span&gt;&amp;#8217;T &lt;span class="caps"&gt;ROLL&lt;/span&gt; &lt;span class="caps"&gt;YOUR&lt;/span&gt; &lt;span class="caps"&gt;OWN&lt;/span&gt; &lt;span class="caps"&gt;CRYPTO&lt;/span&gt;&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;This is all aimed at frivolous pursuits like video games.  Hell, even video games where money is at stake should be deferring to someone who knows way more than I do.  Otherwise you might find out that your deck shuffles in  your poker game are woefully inadequate and some smartass is cheating you out of millions.  (If your random number generator has fewer than 226 bits of state, it can&amp;#8217;t even generate every possible shuffling of a deck of&amp;nbsp;cards!)&lt;/p&gt;
&lt;h2 id="use-the-right-distribution"&gt;&lt;a class="toclink" href="#use-the-right-distribution"&gt;Use the right distribution&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Most languages have a random number primitive that spits out a number uniformly in the&amp;nbsp;range &lt;code&gt;[0, 1)&lt;/code&gt;, and you can go pretty far with just that.  But beware a few&amp;nbsp;traps!&lt;/p&gt;
&lt;h3 id="random-pitches"&gt;&lt;a class="toclink" href="#random-pitches"&gt;Random pitches&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Say you want to pitch up a sound by a random amount, perhaps up to an octave.  Your audio &lt;span class="caps"&gt;API&lt;/span&gt; probably has a way to do this that takes a pitch multiplier, where I say &amp;#8220;probably&amp;#8221; because that&amp;#8217;s how the &lt;a href="https://love2d.org/wiki/Source:setPitch"&gt;only audio &lt;span class="caps"&gt;API&lt;/span&gt; I&amp;#8217;ve used&lt;/a&gt;&amp;nbsp;works.&lt;/p&gt;
&lt;p&gt;Easy peasy.  If 1 is unchanged and 2 is pitched up by an octave, then all you need&amp;nbsp;is &lt;code&gt;rand() + 1&lt;/code&gt;.&amp;nbsp;Right?&lt;/p&gt;
&lt;p&gt;No!  Pitch is &lt;em&gt;exponential&lt;/em&gt; — within the same octave, the &amp;#8220;gap&amp;#8221; between C and C♯ is about half as big as the gap between B and the following C.  If you pick a pitch multiplier uniformly, you&amp;#8217;ll have a noticeable bias towards the higher&amp;nbsp;pitches.&lt;/p&gt;
&lt;p&gt;One octave corresponds to a doubling of pitch, so if you want to pick a random &lt;em&gt;note&lt;/em&gt;, you&amp;nbsp;want &lt;code&gt;2 ** rand()&lt;/code&gt;.&lt;/p&gt;
&lt;h3 id="random-directions"&gt;&lt;a class="toclink" href="#random-directions"&gt;Random directions&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;For two dimensions, you can just pick a random angle&amp;nbsp;with &lt;code&gt;rand() * TAU&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;If you want a vector rather than an angle, or if you want a random direction in &lt;em&gt;three&lt;/em&gt; dimensions, it&amp;#8217;s a little trickier.  You might be tempted to just pick a random point where each component&amp;nbsp;is &lt;code&gt;rand() * 2 - 1&lt;/code&gt; (ranging from −1 to 1), but that&amp;#8217;s not quite right.  A direction is a point on the surface (or, equivalently, within the volume) of a &lt;em&gt;sphere&lt;/em&gt;, and picking each component independently produces a point within the &lt;em&gt;volume of a cube&lt;/em&gt;; the result will be a bias towards the corners of the cube, where there&amp;#8217;s much more extra volume beyond the&amp;nbsp;sphere.&lt;/p&gt;
&lt;p&gt;No?  Well, just trust me.  I don&amp;#8217;t know how to make a diagram for&amp;nbsp;this.&lt;/p&gt;
&lt;p&gt;Anyway, you could use the Pythagorean theorem a few times and make a huge mess of things, &lt;em&gt;or&lt;/em&gt; it turns out there&amp;#8217;s a &lt;a href="http://mathworld.wolfram.com/HyperspherePointPicking.html"&gt;really easy way&lt;/a&gt; that even works for two or four or any number of dimensions.  You pick each coordinate from a &lt;em&gt;Gaussian&lt;/em&gt; (normal) distribution, then normalize the resulting vector.  In other words, using Python&amp;#8217;s &lt;a href="https://docs.python.org/3/library/random.html"&gt;&lt;code&gt;random&lt;/code&gt; module&lt;/a&gt;:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="linenos"&gt;1&lt;/span&gt;&lt;span class="k"&gt;def&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;random_direction&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
&lt;span class="linenos"&gt;2&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;random&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;gauss&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;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="linenos"&gt;3&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;random&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;gauss&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;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="linenos"&gt;4&lt;/span&gt;    &lt;span class="n"&gt;z&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;random&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;gauss&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;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="linenos"&gt;5&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;math&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;sqrt&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="o"&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="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;z&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;z&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="linenos"&gt;6&lt;/span&gt;    &lt;span class="k"&gt;return&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;r&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;r&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;z&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;r&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Why does this work?  I have no&amp;nbsp;idea!&lt;/p&gt;
&lt;p&gt;Note that it &lt;em&gt;is&lt;/em&gt; possible to get zero (or close to it) for every component, in which case the result is nonsense.  You can re-roll all the components if necessary; just check that the &lt;em&gt;magnitude&lt;/em&gt; (or its square) is less than some epsilon, which is equivalent to throwing away a tiny sphere at the center and shouldn&amp;#8217;t affect the&amp;nbsp;distribution.&lt;/p&gt;
&lt;h3 id="beware-gauss"&gt;&lt;a class="toclink" href="#beware-gauss"&gt;Beware Gauss&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Since I brought it up: the Gaussian distribution is a pretty nice one for choosing things in some range, where the middle is the common case and should appear more&amp;nbsp;frequently.&lt;/p&gt;
&lt;p&gt;That said, I never use it, because it has one annoying drawback: the Gaussian distribution has no minimum or maximum value, so you can&amp;#8217;t &lt;em&gt;really&lt;/em&gt; scale it down to the range you want.  In theory, you might get &lt;em&gt;any&lt;/em&gt; value out of it, with no limit on&amp;nbsp;scale.&lt;/p&gt;
&lt;p&gt;In &lt;em&gt;practice&lt;/em&gt;, it&amp;#8217;s astronomically rare to actually get such a value out.  I did a hundred million trials just to see what would happen, and the largest value produced was&amp;nbsp;5.8.&lt;/p&gt;
&lt;p&gt;But, still, I&amp;#8217;d rather not knowingly put extremely rare corner cases in my code if I can at all avoid it.  I could clamp the ends, but that would cause unnatural bunching at the endpoints.  I could reroll if I got a value outside some desired range, but I prefer to avoid rerolling when I can, too; after all, it&amp;#8217;s still (astronomically) possible to have to reroll for an indefinite amount of time.  (Okay, it&amp;#8217;s really not, since you&amp;#8217;ll eventually hit the period of your &lt;span class="caps"&gt;PRNG&lt;/span&gt;.  Still, though.)  I don&amp;#8217;t bend over backwards here — I &lt;em&gt;did&lt;/em&gt; just say to reroll when picking a random direction, after all — but when there&amp;#8217;s a nicer alternative I&amp;#8217;ll gladly use&amp;nbsp;it.&lt;/p&gt;
&lt;p&gt;And lo, there &lt;em&gt;is&lt;/em&gt; a nicer alternative!  Enter the &lt;a href="https://en.wikipedia.org/wiki/Beta_distribution"&gt;beta distribution&lt;/a&gt;.  It always spits out a number&amp;nbsp;in &lt;code&gt;[0, 1]&lt;/code&gt;, so you can easily swap it in for the standard normal function, but it takes two &amp;#8220;shape&amp;#8221; parameters α and β that alter its behavior fairly&amp;nbsp;dramatically.&lt;/p&gt;
&lt;p&gt;With α = β = 1, the beta distribution is uniform, i.e. no different&amp;nbsp;from &lt;code&gt;rand()&lt;/code&gt;.  As α increases, the distribution skews towards the &lt;em&gt;right&lt;/em&gt;, and as β increases, the distribution skews towards the &lt;em&gt;left&lt;/em&gt;.  If α = β, the whole thing is symmetric with a hump in the middle.  The higher either one gets, the more extreme the hump (meaning that value is far more common than any other).  With a little fiddling, you can get a number of interesting&amp;nbsp;curves.&lt;/p&gt;
&lt;p&gt;Screenshots don&amp;#8217;t really do it justice, so here&amp;#8217;s a little Wolfram widget that lets you play with α and β&amp;nbsp;live:&lt;/p&gt;
&lt;iframe src="https://www.open.wolframcloud.com/objects/b6ae9330-d387-42d5-90ca-e41b738f3c78" width="600" height="400" style="margin: 0 auto; border: none;"&gt;&lt;/iframe&gt;

&lt;p&gt;Note that if α = 1, then 1 is a possible value; if β = 1, then 0 is a possible value.  You probably want them both greater than 1, which clamps the endpoints to&amp;nbsp;zero.&lt;/p&gt;
&lt;p&gt;Also, it&amp;#8217;s possible to have either α or β or both be &lt;em&gt;less&lt;/em&gt; than 1, but this creates very different behavior: the corresponding endpoints become &lt;em&gt;poles&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;Anyway, something like α = β = 3 is probably close enough to normal for most purposes but already clamped for you.  And you could easily replicate something like, say, NetHack&amp;#8217;s incredibly bizarre &lt;a href="https://nethackwiki.com/wiki/Rnz"&gt;&lt;code&gt;rnz&lt;/code&gt; function&lt;/a&gt;.&lt;/p&gt;
&lt;h3 id="random-frequency"&gt;&lt;a class="toclink" href="#random-frequency"&gt;Random frequency&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Say you want some event to have an 80% chance to happen every second.  You (who am I kidding, &lt;em&gt;I&lt;/em&gt;) might be tempted to do something like&amp;nbsp;this:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="linenos"&gt;1&lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;random&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="mf"&gt;0.8&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;dt&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="linenos"&gt;2&lt;/span&gt;    &lt;span class="n"&gt;do_thing&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;In an ideal&amp;nbsp;world, &lt;code&gt;dt&lt;/code&gt; is always the same and is equal&amp;nbsp;to &lt;code&gt;1 / f&lt;/code&gt;,&amp;nbsp;where &lt;code&gt;f&lt;/code&gt; is the framerate.  Replace that 80% with a variable,&amp;nbsp;say &lt;code&gt;P&lt;/code&gt;, and every tic you have&amp;nbsp;a &lt;code&gt;P / f&lt;/code&gt; chance to do the… whatever it&amp;nbsp;is.&lt;/p&gt;
&lt;p&gt;Each&amp;nbsp;second, &lt;code&gt;f&lt;/code&gt; tics pass, so you&amp;#8217;ll make this&amp;nbsp;check &lt;code&gt;f&lt;/code&gt; times.  The chance that &lt;em&gt;any&lt;/em&gt; check succeeds is the inverse of the chance that every check fails, which&amp;nbsp;is &lt;span class="math"&gt;\(1 - \left(1 - \frac{P}{f}\right)^f\)&lt;/span&gt;.&lt;/p&gt;
&lt;p&gt;For &lt;code&gt;P&lt;/code&gt; of 80% and a framerate of 60, that&amp;#8217;s a total probability of 55.3%.  Wait,&amp;nbsp;what?&lt;/p&gt;
&lt;p&gt;Consider what happens if the framerate is 2.  On the first tic, you&amp;nbsp;roll &lt;code&gt;0.4&lt;/code&gt; twice — but probabilities are combined by &lt;em&gt;multiplying&lt;/em&gt;, and splitting work up&amp;nbsp;by &lt;code&gt;dt&lt;/code&gt; only works for &lt;em&gt;additive&lt;/em&gt; quantities.  You lose some accuracy along the way.  If you&amp;#8217;re dealing with something that multiplies, you need an exponent&amp;nbsp;somewhere.&lt;/p&gt;
&lt;p&gt;But in this case, maybe you don&amp;#8217;t want that at all.  Each separate roll you make &lt;em&gt;might independently succeed&lt;/em&gt;, so it&amp;#8217;s possible (but very unlikely) that the event will happen 60 times within a single second!  Or 200 times, if that&amp;#8217;s someone&amp;#8217;s&amp;nbsp;framerate.&lt;/p&gt;
&lt;p&gt;If you explicitly want something to have a chance to happen on a specific interval, you have to check on that interval.  If you don&amp;#8217;t have a gizmo handy to run code on an interval, it&amp;#8217;s easy to do yourself with a time&amp;nbsp;buffer:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="linenos"&gt;1&lt;/span&gt;&lt;span class="n"&gt;timer&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="n"&gt;dt&lt;/span&gt;
&lt;span class="linenos"&gt;2&lt;/span&gt;&lt;span class="c1"&gt;# here, 1 is the &amp;quot;every 1 seconds&amp;quot;&lt;/span&gt;
&lt;span class="linenos"&gt;3&lt;/span&gt;&lt;span class="k"&gt;while&lt;/span&gt; &lt;span class="n"&gt;timer&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="linenos"&gt;4&lt;/span&gt;    &lt;span class="n"&gt;timer&lt;/span&gt; &lt;span class="o"&gt;-=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
&lt;span class="linenos"&gt;5&lt;/span&gt;    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;random&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="mf"&gt;0.8&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="linenos"&gt;6&lt;/span&gt;        &lt;span class="n"&gt;do_thing&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Using &lt;code&gt;while&lt;/code&gt; means rolls still happen even if you somehow skipped over an entire&amp;nbsp;second.&lt;/p&gt;
&lt;p&gt;(For the curious, and the nerds who already noticed: the&amp;nbsp;expression &lt;span class="math"&gt;\(1 - \left(1 - \frac{P}{f}\right)^f\)&lt;/span&gt; converges to a specific value!  As the framerate increases, it becomes a better and better approximation&amp;nbsp;for &lt;span class="math"&gt;\(1 - e^{-P}\)&lt;/span&gt;, which for the example above is 0.551.  Hey, 60 fps is pretty accurate — it&amp;#8217;s just accurately representing something nowhere near what I wanted.  Er, you&amp;nbsp;wanted.)&lt;/p&gt;
&lt;h3 id="rolling-your-own"&gt;&lt;a class="toclink" href="#rolling-your-own"&gt;Rolling your own&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Of course, you can fuss with the&amp;nbsp;classic &lt;code&gt;[0, 1]&lt;/code&gt; uniform value however you want.  If I want a bias towards zero, I&amp;#8217;ll often just square it, or multiply two of them together.  If I want a bias towards one, I&amp;#8217;ll take a square root.  If I want something &lt;em&gt;like&lt;/em&gt; a Gaussian/normal distribution, but with clearly-defined endpoints, I might add together &lt;em&gt;n&lt;/em&gt; rolls and divide by &lt;em&gt;n&lt;/em&gt;.  (The normal distribution is just what you get if you roll infinite dice and divide by&amp;nbsp;infinity!)&lt;/p&gt;
&lt;p&gt;It&amp;#8217;d be nice to be able to understand &lt;em&gt;exactly&lt;/em&gt; what this will do to the distribution.  Unfortunately, that requires some calculus, which this post is too small to contain, and which I didn&amp;#8217;t even know much about myself until I went down a deep rabbit hole while writing, and which in many cases is straight up impossible to express&amp;nbsp;directly.&lt;/p&gt;
&lt;p&gt;Here&amp;#8217;s the non-calculus bit.  A source of randomness is often graphed as a &lt;span class="caps"&gt;PDF&lt;/span&gt; — a &lt;a href="https://en.wikipedia.org/wiki/Probability_density_function"&gt;&lt;em&gt;probability density function&lt;/em&gt;&lt;/a&gt;.  You&amp;#8217;ve almost certainly seen a &lt;a href="https://en.wikipedia.org/wiki/Normal_distribution"&gt;bell curve&lt;/a&gt; graphed, and that&amp;#8217;s a &lt;span class="caps"&gt;PDF&lt;/span&gt;.  They&amp;#8217;re pretty nice, since they do exactly what they look like: they show the relative chance that any given value will pop out.  On a bog standard bell curve, there&amp;#8217;s a peak at zero, and of course zero is the most common result from a normal&amp;nbsp;distribution.&lt;/p&gt;
&lt;p&gt;(Okay, actually, since the results are continuous, it&amp;#8217;s vanishingly unlikely that you&amp;#8217;ll get &lt;em&gt;exactly&lt;/em&gt; zero — but you&amp;#8217;re much more likely to get a value &lt;em&gt;near&lt;/em&gt; zero than &lt;em&gt;near&lt;/em&gt; any other&amp;nbsp;number.)&lt;/p&gt;
&lt;p&gt;For the uniform distribution, which is what a&amp;nbsp;classic &lt;code&gt;rand()&lt;/code&gt; gives you, the &lt;span class="caps"&gt;PDF&lt;/span&gt; is just a straight horizontal line — every result is equally&amp;nbsp;likely.&lt;/p&gt;
&lt;hr /&gt;
&lt;p&gt;If there were a calculus bit, it would go here!  Instead, we can cheat.  Sometimes.  Mathematica knows how to work with probability distributions in the abstract, and there&amp;#8217;s a &lt;a href="https://sandbox.open.wolframcloud.com/app/"&gt;free web version&lt;/a&gt; you can use.  For the example of squaring a uniform variable, try this&amp;nbsp;out:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="linenos"&gt;1&lt;/span&gt;&lt;span class="n"&gt;PDF&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;TransformedDistribution&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;u&lt;/span&gt;&lt;span class="o"&gt;^&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;u&lt;/span&gt;&lt;span class="w"&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;Distributed&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;UniformDistribution&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="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="n"&gt;u&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;(The &lt;code&gt;\[Distributed]&lt;/code&gt; is a funny tilde that doesn&amp;#8217;t exist in Unicode, but which Mathematica uses as a first-class operator.  Also, press &lt;kbd&gt;shift&lt;/kbd&gt;&lt;kbd&gt;Enter&lt;/kbd&gt; to evaluate the&amp;nbsp;line.)&lt;/p&gt;
&lt;p&gt;This will tell you that the distribution&amp;nbsp;is…  &lt;span class="math"&gt;\(\frac{1}{2\sqrt{u}}\)&lt;/span&gt;.  Weird!  You can plot&amp;nbsp;it:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="linenos"&gt;1&lt;/span&gt;&lt;span class="n"&gt;Plot&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="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;u&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="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;}]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;(The &lt;code&gt;%&lt;/code&gt; refers to the result of the last thing you did, so if you want to try several of these, you can just&amp;nbsp;do &lt;code&gt;Plot[PDF[…], u]&lt;/code&gt; directly.)&lt;/p&gt;
&lt;p&gt;The resulting graph shows that numbers around zero are, in fact, vastly — &lt;em&gt;infinitely&lt;/em&gt; — more likely than anything&amp;nbsp;else.&lt;/p&gt;
&lt;p&gt;What about multiplying two together?  I can&amp;#8217;t figure out how to get Mathematica to understand this, but a great amount of digging revealed that the answer&amp;nbsp;is &lt;code&gt;-ln x&lt;/code&gt;, and from there you can &lt;a href="http://www.wolframalpha.com/input/?i=1%2F%282sqrt%28x%29%29,+-ln+x+from+0+to+1"&gt;plot them both&lt;/a&gt; on Wolfram Alpha.  They&amp;#8217;re &lt;em&gt;similar&lt;/em&gt;, though squaring has a much better chance of giving you high numbers than multiplying two separate rolls — which makes some sense, since if &lt;em&gt;either&lt;/em&gt; of two rolls is a low number, the product will be even&amp;nbsp;lower.&lt;/p&gt;
&lt;p&gt;What if you know the graph you want, and you want to figure out how to play with a uniform roll to get it?  Good news!  That&amp;#8217;s a whole thing called &lt;a href="https://en.wikipedia.org/wiki/Inverse_transform_sampling"&gt;inverse transform sampling&lt;/a&gt;.  All you have to do is take an integral.  Good&amp;nbsp;luck!&lt;/p&gt;
&lt;hr /&gt;
&lt;p&gt;This is all extremely ridiculous.  New tactic: &lt;strong&gt;Just Simulate The Damn Thing&lt;/strong&gt;.  You already &lt;em&gt;have&lt;/em&gt; the code; run it a million times, make a histogram, and tada, there&amp;#8217;s your &lt;span class="caps"&gt;PDF&lt;/span&gt;.  That&amp;#8217;s one of the great things about computers!  Brute-force numerical answers are easy to come by, so there&amp;#8217;s no excuse for producing something like &lt;a href="https://nethackwiki.com/wiki/Rnz"&gt;rnz&lt;/a&gt;.  (Though, be sure your histogram has sufficiently narrow buckets — I tried plotting one for rnz once and the weird stuff on the left side didn&amp;#8217;t show up at&amp;nbsp;all!)&lt;/p&gt;
&lt;p&gt;By the way, I learned something from futzing with Mathematica here!  Taking the square root (to bias towards 1) gives a &lt;span class="caps"&gt;PDF&lt;/span&gt; that&amp;#8217;s a straight diagonal line, nothing like the hyperbola you get from squaring (to bias towards 0).  How do you get a straight line the other way?&amp;nbsp;Surprise: &lt;span class="math"&gt;\(1 - \sqrt{1 - u}\)&lt;/span&gt;.&lt;/p&gt;
&lt;h3 id="okay-okay-heres-the-actual-math"&gt;&lt;a class="toclink" href="#okay-okay-heres-the-actual-math"&gt;Okay, okay, here's the actual math&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;I don&amp;#8217;t claim to have a very firm grasp on this, but I had a hell of a time finding it written out clearly, so I might as well write it down as best I can.  This was a great excuse to finally set up MathJax,&amp;nbsp;too.&lt;/p&gt;
&lt;p&gt;Say &lt;span class="math"&gt;\(u(x)\)&lt;/span&gt; is the &lt;span class="caps"&gt;PDF&lt;/span&gt; of the original distribution&amp;nbsp;and &lt;span class="math"&gt;\(u\)&lt;/span&gt; is a representative number you plucked from that distribution.  For the uniform&amp;nbsp;distribution, &lt;span class="math"&gt;\(u(x) = 1\)&lt;/span&gt;.  Or, more&amp;nbsp;accurately,&lt;/p&gt;
&lt;div class="math"&gt;$$
u(x) = \begin{cases}
1 &amp;amp; \text{ if } 0 \le x \lt 1 \\
0 &amp;amp; \text{ otherwise }
\end{cases}
$$&lt;/div&gt;
&lt;p&gt;Remember&amp;nbsp;that &lt;span class="math"&gt;\(x\)&lt;/span&gt; here is a possible outcome you want to know about, and the &lt;span class="caps"&gt;PDF&lt;/span&gt; tells you the relative probability that a roll will be &lt;em&gt;near&lt;/em&gt; it.  This &lt;span class="caps"&gt;PDF&lt;/span&gt; spits out 1 for&amp;nbsp;every &lt;span class="math"&gt;\(x\)&lt;/span&gt;, meaning every number between 0 and 1 is equally likely to&amp;nbsp;appear.&lt;/p&gt;
&lt;p&gt;We want to &lt;em&gt;do something&lt;/em&gt; to that &lt;span class="caps"&gt;PDF&lt;/span&gt;, which creates a new distribution, whose &lt;span class="caps"&gt;PDF&lt;/span&gt; we want to know.  I&amp;#8217;ll use my original example&amp;nbsp;of &lt;span class="math"&gt;\(f(u) = u^2\)&lt;/span&gt;, which creates a new &lt;span class="caps"&gt;PDF&lt;/span&gt; &lt;span class="math"&gt;\(v(x)\)&lt;/span&gt;.&lt;/p&gt;
&lt;p&gt;The trick is that we need to work in terms of the &lt;em&gt;cumulative&lt;/em&gt; distribution function&amp;nbsp;for &lt;span class="math"&gt;\(u\)&lt;/span&gt;.  Where the &lt;span class="caps"&gt;PDF&lt;/span&gt; gives the relative chance that a roll will be (&amp;#8220;near&amp;#8221;) a specific value, the &lt;span class="caps"&gt;CDF&lt;/span&gt; gives the relative chance that a roll will be &lt;em&gt;less than&lt;/em&gt; a specific&amp;nbsp;value.&lt;/p&gt;
&lt;p&gt;The conventions for this seem to be a bit fuzzy, and nobody bothers to explain which ones they&amp;#8217;re using, which makes this all the more confusing to read about…  but let&amp;#8217;s write the &lt;span class="caps"&gt;CDF&lt;/span&gt; with a capital letter, so we&amp;nbsp;have &lt;span class="math"&gt;\(U(x)\)&lt;/span&gt;.  In this&amp;nbsp;case, &lt;span class="math"&gt;\(U(x) = x\)&lt;/span&gt;, a straight 45° line (at least between 0 and 1).  With the definition I gave, this should make sense.  At some arbitrary point like 0.4, the value of the &lt;span class="caps"&gt;PDF&lt;/span&gt; is 1 (0.4 is just as likely as anything else), and the value of the &lt;span class="caps"&gt;CDF&lt;/span&gt; is 0.4 (you have a 40% chance of getting a number from 0 to&amp;nbsp;0.4).&lt;/p&gt;
&lt;p&gt;Calculus ahoy: the &lt;span class="caps"&gt;PDF&lt;/span&gt; is the &lt;em&gt;derivative&lt;/em&gt; of the &lt;span class="caps"&gt;CDF&lt;/span&gt;, which means it measures the &lt;em&gt;slope&lt;/em&gt; of the &lt;span class="caps"&gt;CDF&lt;/span&gt; at any point.&amp;nbsp;For &lt;span class="math"&gt;\(U(x) = x\)&lt;/span&gt;, the slope is always 1, and&amp;nbsp;indeed &lt;span class="math"&gt;\(u(x) = 1\)&lt;/span&gt;.  See, calculus is&amp;nbsp;easy.&lt;/p&gt;
&lt;p&gt;Okay, so, now we&amp;#8217;re getting somewhere.  What we want is the &lt;em&gt;&lt;span class="caps"&gt;CDF&lt;/span&gt;&lt;/em&gt; of our new&amp;nbsp;distribution, &lt;span class="math"&gt;\(V(x)\)&lt;/span&gt;.  The &lt;span class="caps"&gt;CDF&lt;/span&gt; is defined as the probability that a&amp;nbsp;roll &lt;span class="math"&gt;\(v\)&lt;/span&gt; will be less&amp;nbsp;than &lt;span class="math"&gt;\(x\)&lt;/span&gt;, so we can literally&amp;nbsp;write:&lt;/p&gt;
&lt;div class="math"&gt;$$V(x) = P(v \le x)$$&lt;/div&gt;
&lt;p&gt;(This is why we have to work with CDFs, rather than PDFs — a &lt;span class="caps"&gt;PDF&lt;/span&gt; gives the chance that a roll will be &amp;#8220;nearby,&amp;#8221; whatever that means.  A &lt;span class="caps"&gt;CDF&lt;/span&gt; is much more&amp;nbsp;concrete.)&lt;/p&gt;
&lt;p&gt;What&amp;nbsp;is &lt;span class="math"&gt;\(v\)&lt;/span&gt;, exactly?  We defined it ourselves; it&amp;#8217;s the &lt;em&gt;do something&lt;/em&gt; applied to a roll from the original distribution,&amp;nbsp;or &lt;span class="math"&gt;\(f(u)\)&lt;/span&gt;.&lt;/p&gt;
&lt;div class="math"&gt;$$V(x) = P\!\left(f(u) \le x\right)$$&lt;/div&gt;
&lt;p&gt;Now the first tricky part: we have to solve that inequality&amp;nbsp;for &lt;span class="math"&gt;\(u\)&lt;/span&gt;, which means we have to &lt;em&gt;do something, backwards&lt;/em&gt;&amp;nbsp;to &lt;span class="math"&gt;\(x\)&lt;/span&gt;.&lt;/p&gt;
&lt;div class="math"&gt;$$V(x) = P\!\left(u \le f^{-1}(x)\right)$$&lt;/div&gt;
&lt;p&gt;Almost there!  We now have a probability&amp;nbsp;that &lt;span class="math"&gt;\(u\)&lt;/span&gt; is less than some value, and that&amp;#8217;s the definition of a &lt;span class="caps"&gt;CDF&lt;/span&gt;!&lt;/p&gt;
&lt;div class="math"&gt;$$V(x) = U\!\left(f^{-1}(x)\right)$$&lt;/div&gt;
&lt;p&gt;Hooray!  Now to turn these CDFs back into PDFs, all we need to do is differentiate both sides and use the chain rule.  If you never took calculus, don&amp;#8217;t worry too much about what that&amp;nbsp;means!&lt;/p&gt;
&lt;div class="math"&gt;$$v(x) = u\!\left(f^{-1}(x)\right)\left|\frac{d}{dx}f^{-1}(x)\right|$$&lt;/div&gt;
&lt;p&gt;Wait!  Where did that absolute value come from?  It takes care of&amp;nbsp;whether &lt;span class="math"&gt;\(f(x)\)&lt;/span&gt; increases or decreases.  It&amp;#8217;s the least interesting part here by far, so,&amp;nbsp;whatever.&lt;/p&gt;
&lt;p&gt;There&amp;#8217;s one more magical part here when using the uniform distribution&amp;nbsp;— &lt;span class="math"&gt;\(u(\dots)\)&lt;/span&gt; is always equal to 1, so that entire term disappears!  (Note that this only works for a uniform distribution with a width of 1; PDFs are scaled so the entire area under them sums to 1, so if you had&amp;nbsp;a &lt;code&gt;rand()&lt;/code&gt; that could spit out a number between 0 and 2, the &lt;span class="caps"&gt;PDF&lt;/span&gt; would&amp;nbsp;be &lt;span class="math"&gt;\(u(x) = \frac{1}{2}\)&lt;/span&gt;.)&lt;/p&gt;
&lt;div class="math"&gt;$$v(x) = \left|\frac{d}{dx}f^{-1}(x)\right|$$&lt;/div&gt;
&lt;p&gt;So for the specific case of modifying the output&amp;nbsp;of &lt;code&gt;rand()&lt;/code&gt;, all we have to do is invert, then differentiate.  The inverse&amp;nbsp;of &lt;span class="math"&gt;\(f(u) = u^2\)&lt;/span&gt; is &lt;span class="math"&gt;\(f^{-1}(x) = \sqrt{x}\)&lt;/span&gt; (no need for a ± since we&amp;#8217;re only dealing with positive numbers), and differentiating that&amp;nbsp;gives &lt;span class="math"&gt;\(v(x) = \frac{1}{2\sqrt{x}}\)&lt;/span&gt;.  Done!  This is also why square root comes out nicer; inverting it&amp;nbsp;gives &lt;span class="math"&gt;\(x^2\)&lt;/span&gt;, and differentiating that&amp;nbsp;gives &lt;span class="math"&gt;\(2x\)&lt;/span&gt;, a straight&amp;nbsp;line.&lt;/p&gt;
&lt;p&gt;Incidentally, that method for turning a uniform distribution into &lt;em&gt;any&lt;/em&gt; distribution — &lt;a href="https://en.wikipedia.org/wiki/Inverse_transform_sampling"&gt;inverse transform sampling&lt;/a&gt; — is pretty much the same thing in reverse: integrate, then invert.  For example, when I saw that taking the square root&amp;nbsp;gave &lt;span class="math"&gt;\(v(x) = 2x\)&lt;/span&gt;, I naturally wondered how to get a straight line going the other&amp;nbsp;way, &lt;span class="math"&gt;\(v(x) = 2 - 2x\)&lt;/span&gt;.  Integrating that&amp;nbsp;gives &lt;span class="math"&gt;\(2x - x^2\)&lt;/span&gt;, and then you can use the quadratic formula (or just ask Wolfram Alpha) to&amp;nbsp;solve &lt;span class="math"&gt;\(2x - x^2 = u\)&lt;/span&gt; for &lt;span class="math"&gt;\(x\)&lt;/span&gt; and&amp;nbsp;get &lt;span class="math"&gt;\(f(u) = 1 - \sqrt{1 - u}\)&lt;/span&gt;.&lt;/p&gt;
&lt;p&gt;Multiply &lt;em&gt;two&lt;/em&gt; rolls is a bit more complicated; you have to write out the &lt;span class="caps"&gt;CDF&lt;/span&gt; as an integral and you end up doing a double integral and wow it&amp;#8217;s a mess.  The only thing I&amp;#8217;ve retained is that you do a division somewhere, which then gets integrated, and that&amp;#8217;s why it ends up&amp;nbsp;as &lt;span class="math"&gt;\(-\ln x\)&lt;/span&gt;.&lt;/p&gt;
&lt;p&gt;And that&amp;#8217;s quite enough of that!  (Okay but having math in my blog is pretty cool and I will definitely be doing more of this, sorry, not&amp;nbsp;sorry.)&lt;/p&gt;
&lt;h2 id="random-vs-varied"&gt;&lt;a class="toclink" href="#random-vs-varied"&gt;Random vs varied&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Sometimes, &lt;em&gt;random&lt;/em&gt; isn&amp;#8217;t actually what you want.  We tend to use the word &amp;#8220;random&amp;#8221; casually to mean something more like &lt;em&gt;chaotic&lt;/em&gt;, i.e., with no discernible pattern.  But that&amp;#8217;s not really random.  In fact, given how good humans can be at finding incidental patterns, they aren&amp;#8217;t all that unlikely!  Consider that when you roll two dice, they&amp;#8217;ll come up either the same or only one apart almost half the time.  &lt;em&gt;Coincidence?&lt;/em&gt;  Well,&amp;nbsp;yes.&lt;/p&gt;
&lt;p&gt;If you ask for randomness, you&amp;#8217;re saying that &lt;em&gt;any&lt;/em&gt; outcome — or series of outcomes — is acceptable, including five heads in a row or five tails in a row.  Most of the time, that&amp;#8217;s fine.  Some of the time, it&amp;#8217;s less fine, and what you really want is &lt;em&gt;variety&lt;/em&gt;.  Here are a couple examples and some fairly easy&amp;nbsp;workarounds.&lt;/p&gt;
&lt;h3 id="npc-quips"&gt;&lt;a class="toclink" href="#npc-quips"&gt;NPC quips&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;The nature of games is such that NPCs will eventually run out of things to say, at which point further conversation will give the player a short brush-off quip — a slight nod from the designer to the player that, hey, you hit the end of the&amp;nbsp;script.&lt;/p&gt;
&lt;p&gt;Some NPCs have multiple possible quips and will give one at random.  The trouble with this is that it&amp;#8217;s very possible for an &lt;span class="caps"&gt;NPC&lt;/span&gt; to repeat the &lt;em&gt;same&lt;/em&gt; quip several times in a row before abruptly switching to another one.  With only a few options to choose from, getting the same option twice or thrice (especially across an entire game, which may have numerous NPCs) isn&amp;#8217;t all that unlikely.  The notion of an &lt;span class="caps"&gt;NPC&lt;/span&gt; quip isn&amp;#8217;t very realistic to start with, but having someone repeat themselves and then abruptly switch to something else is especially&amp;nbsp;jarring.&lt;/p&gt;
&lt;p&gt;The easy fix is to show the quips in order!  Paradoxically, this is more consistently varied than choosing at random — the original &amp;#8220;order&amp;#8221; is likely to be meaningless anyway, and it already has the property that the same quip can never appear twice in a&amp;nbsp;row.&lt;/p&gt;
&lt;p&gt;If you like, you can shuffle the list of quips every time you reach the end, but take care here — it&amp;#8217;s possible that the last quip in the old order will be the same as the first quip in the new order, so you may still get a repeat.  (Of course, you can just check for this case and swap the first quip somewhere else if it bothers&amp;nbsp;you.)&lt;/p&gt;
&lt;p&gt;That last behavior is, in fact, the canonical way that &lt;a href="https://harddrop.com/wiki/Random_Generator"&gt;Tetris chooses pieces&lt;/a&gt; — the game simply shuffles a list of all 7 pieces, gives those to you in shuffled order, then shuffles them again to make a new list once it&amp;#8217;s exhausted.  There&amp;#8217;s no avoidance of duplicates, though, so you can still get two S blocks in a row, or even two S and two Z all clumped together, but no more than that.  Some Tetris variants take other approaches, such as &lt;a href="https://harddrop.com/wiki/TGM_randomizer"&gt;actively avoiding repeats even several pieces apart&lt;/a&gt; or &lt;a href="http://blahg.res0l.net/2009/01/bastet-bastard-tetris/"&gt;deliberately giving you the worst piece possible&lt;/a&gt;.&lt;/p&gt;
&lt;h3 id="random-drops"&gt;&lt;a class="toclink" href="#random-drops"&gt;Random drops&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Random drops are often implemented as a flat chance each time.  Maybe enemies have a 5% chance to drop health when they die.  &lt;a href="https://en.wikipedia.org/wiki/Law_of_large_numbers"&gt;Legally speaking&lt;/a&gt;, over the long term, a player will see health drops for about 5% of enemy&amp;nbsp;kills.&lt;/p&gt;
&lt;p&gt;Over the &lt;em&gt;short&lt;/em&gt; term, they may be desperate for health and not survive to see the long term.  So you may want to put a thumb on the scale sometimes.  Games in the Metroid series, for example, have a somewhat infamous bias towards whatever kind of drop they think you need — health if your health is low, missiles if your missiles are&amp;nbsp;low.&lt;/p&gt;
&lt;p&gt;I can&amp;#8217;t give you an exact approach to use, since it depends on the game and the feeling you&amp;#8217;re going for and the variables at your disposal.  In extreme cases, you might want to &lt;em&gt;guarantee&lt;/em&gt; a health drop from a tough enemy when the player is critically low on health.  (Or if you&amp;#8217;re feeling particularly evil, you could go the other way and deny the player health when they most need&amp;nbsp;it…)&lt;/p&gt;
&lt;p&gt;The problem becomes a little different, and worse, when the event that triggers the drop is relatively rare.  The pathological case here would be something like a raid boss in World of Warcraft, which requires hours of effort from a coordinated group of people to defeat, and which has some tiny chance of dropping a good item that will go to only one of those people.  This is why I stopped playing World of Warcraft at&amp;nbsp;60.&lt;/p&gt;
&lt;p&gt;Dialing it back a little bit gives us Enter the Gungeon, a roguelike where each room is a set of encounters and each floor only has a dozen or so rooms.  Initially, you have a 1% chance of getting a reward after completing a room — but every time you complete a room and &lt;em&gt;don&amp;#8217;t&lt;/em&gt; get a reward, the chance increases by 9%, up to a cap of 80%.  Once you get a reward, the chance resets to&amp;nbsp;1%.&lt;/p&gt;
&lt;p&gt;The natural question is: how frequently, exactly, can a player expect to get a reward?  We could do math, or we could Just Simulate The Damn&amp;nbsp;Thing.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="linenos"&gt; 1&lt;/span&gt;&lt;span class="kn"&gt;from&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;collections&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Counter&lt;/span&gt;
&lt;span class="linenos"&gt; 2&lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;random&lt;/span&gt;
&lt;span class="linenos"&gt; 3&lt;/span&gt;
&lt;span class="linenos"&gt; 4&lt;/span&gt;&lt;span class="n"&gt;histogram&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Counter&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="linenos"&gt; 5&lt;/span&gt;
&lt;span class="linenos"&gt; 6&lt;/span&gt;&lt;span class="n"&gt;TRIALS&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1000000&lt;/span&gt;
&lt;span class="linenos"&gt; 7&lt;/span&gt;&lt;span class="n"&gt;chance&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
&lt;span class="linenos"&gt; 8&lt;/span&gt;&lt;span class="n"&gt;rooms_cleared&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;
&lt;span class="linenos"&gt; 9&lt;/span&gt;&lt;span class="n"&gt;rewards_found&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;
&lt;span class="linenos"&gt;10&lt;/span&gt;&lt;span class="k"&gt;while&lt;/span&gt; &lt;span class="n"&gt;rewards_found&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="n"&gt;TRIALS&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="linenos"&gt;11&lt;/span&gt;    &lt;span class="n"&gt;rooms_cleared&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
&lt;span class="linenos"&gt;12&lt;/span&gt;    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;random&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;random&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;100&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="n"&gt;chance&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="linenos"&gt;13&lt;/span&gt;        &lt;span class="c1"&gt;# Reward!&lt;/span&gt;
&lt;span class="linenos"&gt;14&lt;/span&gt;        &lt;span class="n"&gt;rewards_found&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
&lt;span class="linenos"&gt;15&lt;/span&gt;        &lt;span class="n"&gt;histogram&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;rooms_cleared&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
&lt;span class="linenos"&gt;16&lt;/span&gt;        &lt;span class="n"&gt;rooms_cleared&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;
&lt;span class="linenos"&gt;17&lt;/span&gt;        &lt;span class="n"&gt;chance&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
&lt;span class="linenos"&gt;18&lt;/span&gt;    &lt;span class="k"&gt;else&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="linenos"&gt;19&lt;/span&gt;        &lt;span class="n"&gt;chance&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;min&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;80&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;chance&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;9&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="linenos"&gt;20&lt;/span&gt;
&lt;span class="linenos"&gt;21&lt;/span&gt;&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;gaps&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;count&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nb"&gt;sorted&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;histogram&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;items&lt;/span&gt;&lt;span class="p"&gt;()):&lt;/span&gt;
&lt;span class="linenos"&gt;22&lt;/span&gt;    &lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;gaps&lt;/span&gt;&lt;span class="si"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;3d&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; | &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;count&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;TRIALS&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;100&lt;/span&gt;&lt;span class="si"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;6.2f&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;%&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;#&amp;#39;&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;count&lt;/span&gt; &lt;span class="o"&gt;//&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;TRIALS&lt;/span&gt; &lt;span class="o"&gt;//&lt;/span&gt; &lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;)))&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="linenos"&gt; 1&lt;/span&gt;  1 |   0.98%
&lt;span class="linenos"&gt; 2&lt;/span&gt;  2 |   9.91% #########
&lt;span class="linenos"&gt; 3&lt;/span&gt;  3 |  17.00% ################
&lt;span class="linenos"&gt; 4&lt;/span&gt;  4 |  20.23% ####################
&lt;span class="linenos"&gt; 5&lt;/span&gt;  5 |  19.21% ###################
&lt;span class="linenos"&gt; 6&lt;/span&gt;  6 |  15.05% ###############
&lt;span class="linenos"&gt; 7&lt;/span&gt;  7 |   9.69% #########
&lt;span class="linenos"&gt; 8&lt;/span&gt;  8 |   5.07% #####
&lt;span class="linenos"&gt; 9&lt;/span&gt;  9 |   2.09% ##
&lt;span class="linenos"&gt;10&lt;/span&gt; 10 |   0.63%
&lt;span class="linenos"&gt;11&lt;/span&gt; 11 |   0.12%
&lt;span class="linenos"&gt;12&lt;/span&gt; 12 |   0.03%
&lt;span class="linenos"&gt;13&lt;/span&gt; 13 |   0.00%
&lt;span class="linenos"&gt;14&lt;/span&gt; 14 |   0.00%
&lt;span class="linenos"&gt;15&lt;/span&gt; 15 |   0.00%
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;We&amp;#8217;ve got kind of a hilly distribution, skewed to the left, which is up in this histogram.  Most of the time, a player should see a reward every three to six rooms, which is maybe twice per floor.  It&amp;#8217;s vanishingly unlikely to go through a dozen rooms without ever seeing a reward, so a player &lt;em&gt;should&lt;/em&gt; see at least one per&amp;nbsp;floor.&lt;/p&gt;
&lt;p&gt;Of course, this simulated a single continuous playthrough; when starting the game from scratch, your chance at a reward always starts fresh at 1%, the worst it can be.  If you want to know about how many rewards a player will get on the first floor, hey, Just Simulate The Damn&amp;nbsp;Thing.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="linenos"&gt;1&lt;/span&gt;  0 |   0.01%
&lt;span class="linenos"&gt;2&lt;/span&gt;  1 |  13.01% #############
&lt;span class="linenos"&gt;3&lt;/span&gt;  2 |  56.28% ########################################################
&lt;span class="linenos"&gt;4&lt;/span&gt;  3 |  27.49% ###########################
&lt;span class="linenos"&gt;5&lt;/span&gt;  4 |   3.10% ###
&lt;span class="linenos"&gt;6&lt;/span&gt;  5 |   0.11%
&lt;span class="linenos"&gt;7&lt;/span&gt;  6 |   0.00%
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Cool.  Though, that&amp;#8217;s assuming exactly 12 rooms; it might be worth changing that to pick at random in a way that matches the level&amp;nbsp;generator.&lt;/p&gt;
&lt;p&gt;(Enter the Gungeon does some other things to skew probability, which is very nice in a roguelike where blind luck can make or break you.  For example, if you kill a boss without having gotten a new gun anywhere else on the floor, the boss is guaranteed to drop a&amp;nbsp;gun.)&lt;/p&gt;
&lt;h3 id="critical-hits"&gt;&lt;a class="toclink" href="#critical-hits"&gt;Critical hits&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;I suppose this is the same problem as random drops, but&amp;nbsp;backwards.&lt;/p&gt;
&lt;p&gt;Say you have a battle sim where every attack has a 6% chance to land a devastating critical hit.  Presumably the same rules apply to both the player and the &lt;span class="caps"&gt;AI&lt;/span&gt;&amp;nbsp;opponents.&lt;/p&gt;
&lt;p&gt;Consider, then, that the &lt;span class="caps"&gt;AI&lt;/span&gt; opponents have exactly the same 6% chance to ruin the player&amp;#8217;s day.  Consider also that this gives them an 0.4% chance to critical hit &lt;em&gt;twice in a row&lt;/em&gt;.  0.4% doesn&amp;#8217;t sound like much, but across an entire playthrough, it&amp;#8217;s not unlikely that a player might see it happen and find it incredibly&amp;nbsp;annoying.&lt;/p&gt;
&lt;p&gt;Perhaps it would be worthwhile to explicitly forbid &lt;span class="caps"&gt;AI&lt;/span&gt; opponents from getting consecutive critical&amp;nbsp;hits.&lt;/p&gt;
&lt;h2 id="in-conclusion"&gt;&lt;a class="toclink" href="#in-conclusion"&gt;In conclusion&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;An emerging theme here has been to Just Simulate The Damn Thing.  So consider Just Simulating The Damn Thing.  Even a simple change to a random value can do surprising things to the resulting distribution, so unless you feel like differentiating the inverse function of &lt;em&gt;your code&lt;/em&gt;, maybe test out any non-trivial behavior and make sure it&amp;#8217;s what you wanted.  Probability is &lt;a href="https://en.wikipedia.org/wiki/Monty_Hall_problem"&gt;hard to reason about&lt;/a&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="articles"></category><category term="tech"></category><category term="patreon"></category><category term="math"></category></entry><entry><title>Game night 1: Lisa, Lisa, MOOP</title><link href="https://eev.ee/blog/2017/12/05/game-night-1-lisa-lisa-moop/" rel="alternate"></link><published>2017-12-05T23:39:00-08:00</published><updated>2017-12-05T23:39:00-08:00</updated><author><name>Eevee</name></author><id>tag:eev.ee,2017-12-05:/blog/2017/12/05/game-night-1-lisa-lisa-moop/</id><summary type="html">&lt;p&gt;For the last few weeks, glip (my partner) and I have spent a couple hours most nights playing indie games together.  We started out intending to play a short list of games that had been recommended to glip, but this turns out to be a nice way to wind down, so we’ve been keeping it up and clicking on whatever looks interesting in the &lt;a href="https://itch.io/app"&gt;itch app&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Most of the games are small and made by one or two people, so they tend to be pretty tightly scoped and focus on a few particular kinds of details.  I’ve found myself having brain thoughts about all that, so I thought I’d write some of them down.&lt;/p&gt;
&lt;p&gt;I also know that some people (cough) tend not to play games they’ve never heard of, even if they &lt;em&gt;want&lt;/em&gt; something new to play.  If that’s you, feel free to play some of these, now that you’ve heard of them!&lt;/p&gt;
&lt;p&gt;Also, I’m still figuring the format out here, so let me know if this is interesting or if you hope I never do it again!&lt;/p&gt;
&lt;p&gt;First up:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Lisa: The Painful&lt;/li&gt;
&lt;li&gt;Lisa: The Joyful&lt;/li&gt;
&lt;li&gt;&lt;span class="caps"&gt;MOOP&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;em&gt;These are impressions, not reviews.  I try to avoid major/ending spoilers, but big plot points do tend to leave impressions.&lt;/em&gt;&lt;/p&gt;
</summary><content type="html">&lt;p&gt;For the last few weeks, glip (my partner) and I have spent a couple hours most nights playing indie games together.  We started out intending to play a short list of games that had been recommended to glip, but this turns out to be a nice way to wind down, so we&amp;#8217;ve been keeping it up and clicking on whatever looks interesting in the &lt;a href="https://itch.io/app"&gt;itch app&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Most of the games are small and made by one or two people, so they tend to be pretty tightly scoped and focus on a few particular kinds of details.  I&amp;#8217;ve found myself having brain thoughts about all that, so I thought I&amp;#8217;d write some of them&amp;nbsp;down.&lt;/p&gt;
&lt;p&gt;I also know that some people (cough) tend not to play games they&amp;#8217;ve never heard of, even if they &lt;em&gt;want&lt;/em&gt; something new to play.  If that&amp;#8217;s you, feel free to play some of these, now that you&amp;#8217;ve heard of&amp;nbsp;them!&lt;/p&gt;
&lt;p&gt;Also, I&amp;#8217;m still figuring the format out here, so let me know if this is interesting or if you hope I never do it&amp;nbsp;again!&lt;/p&gt;
&lt;p&gt;First&amp;nbsp;up:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Lisa: The&amp;nbsp;Painful&lt;/li&gt;
&lt;li&gt;Lisa: The&amp;nbsp;Joyful&lt;/li&gt;
&lt;li&gt;&lt;span class="caps"&gt;MOOP&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;em&gt;These are impressions, not reviews.  I try to avoid major/ending spoilers, but big plot points do tend to leave&amp;nbsp;impressions.&lt;/em&gt;&lt;/p&gt;


&lt;h2 id="lisa-the-painful"&gt;&lt;a class="toclink" href="#lisa-the-painful"&gt;Lisa: The Painful&lt;/a&gt;&lt;/h2&gt;
&lt;div class="prose-full-illustration"&gt;
&lt;img src="https://eev.ee/media/game-night/lisa-the-painful.png" alt=""&gt;
&lt;/div&gt;

&lt;p&gt;&lt;center&gt;&lt;em&gt;long · classic rpg · dec 2014 · lin/mac/win · $10 on &lt;a href="https://dingalingboy.itch.io/lisathepainfulrpg"&gt;itch&lt;/a&gt; or &lt;a href="http://store.steampowered.com/app/335670"&gt;steam&lt;/a&gt; · &lt;a href="http://www.lisatherpg.com/"&gt;website&lt;/a&gt;&lt;/em&gt;&lt;/center&gt;&lt;/p&gt;
&lt;p&gt;(&lt;strong&gt;cw: basically everything??&lt;/strong&gt;)&lt;/p&gt;
&lt;p&gt;Lisa: The Painful is true to its name.  I hesitate to describe it as &lt;em&gt;fun&lt;/em&gt;, exactly, but I&amp;#8217;m glad we played&amp;nbsp;it.&lt;/p&gt;
&lt;p&gt;Everything about the game is &lt;strong&gt;dark&lt;/strong&gt;.  It&amp;#8217;s a (somewhat loose) sequel to another game called Lisa, whose titular character ultimately commits suicide; her body hanging from a noose is the title screen for this&amp;nbsp;game.&lt;/p&gt;
&lt;p&gt;Ah, but don&amp;#8217;t worry, it gets worse.  This game takes place in a post-apocalyptic wasteland, where every female human — women, children, babies — is dead.  You play as Brad (Lisa&amp;#8217;s brother), who has discovered the lone exception: a baby girl he names Buddy and raises like a daughter.  Now, Buddy has been kidnapped, and you have to go rescue her, presumably from being&amp;nbsp;raped.&lt;/p&gt;
&lt;p&gt;Ah, but don&amp;#8217;t worry, it gets&amp;nbsp;worse.&lt;/p&gt;
&lt;hr /&gt;
&lt;p&gt;I&amp;#8217;ve had a hard time putting my thoughts in order here, because so much of what stuck with me is the way the game entangles the plot with the&amp;nbsp;mechanics.&lt;/p&gt;
&lt;p&gt;I &lt;em&gt;love&lt;/em&gt; that kind of thing, but it&amp;#8217;s so hard to do well.  I can&amp;#8217;t really explain why, but I feel like most attempts to do it fall flat — they have a glimmer of an idea, but they don&amp;#8217;t integrate it well enough, or they don&amp;#8217;t run nearly as far as they could have.  I often get the same feeling as, say, a hyped-up big moral choice that turns out to be picking &amp;#8220;yes&amp;#8221; or &amp;#8220;no&amp;#8221; from a menu.  The &lt;em&gt;idea&lt;/em&gt; is there, but the execution is so flimsy that it leaves no impact on me at&amp;nbsp;all.&lt;/p&gt;
&lt;p&gt;An obvious recent success here is Undertale, where the entire story is about violence and whether you choose to engage or avoid it (and whether you &lt;em&gt;can&lt;/em&gt; do that).  If you choose to eschew violence, not only does the game become more difficult, it arguably becomes a different game entirely.  Granted, the contrast is lost if you (like me) tried to play as a pacifist from the very beginning.  I do feel that you could go further with the idea than Undertale, but Undertale itself doesn&amp;#8217;t feel &lt;em&gt;incomplete&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;Christ, I&amp;#8217;m not even talking about the right game any&amp;nbsp;more.&lt;/p&gt;
&lt;p&gt;Okay, so: this game is a &amp;#8220;classic&amp;#8221; &lt;span class="caps"&gt;RPG&lt;/span&gt;, by which I mean, it was made with &lt;span class="caps"&gt;RPG&lt;/span&gt; Maker.  (It&amp;#8217;s kinda funny that &lt;span class="caps"&gt;RPG&lt;/span&gt; Maker was designed to emulate a very popular battle style, and now the only games that use that style are…  made with &lt;span class="caps"&gt;RPG&lt;/span&gt; Maker.)  The main loop, on the surface, is standard &lt;span class="caps"&gt;RPG&lt;/span&gt; fare: you walk around various places, talk to people, solve puzzles, recruit party members, and get into turn-based&amp;nbsp;fights.&lt;/p&gt;
&lt;p&gt;Now, Brad is addicted to a drug called Joy.  He will regularly go into withdrawal, which manifests in the game as a &lt;em&gt;status effect&lt;/em&gt; that cuts his stats (even his max &lt;span class="caps"&gt;HP&lt;/span&gt;!)&amp;nbsp;dramatically.&lt;/p&gt;
&lt;p&gt;It is really, really, incredibly inconvenient.  And therein lies the genius here.  The game could have simply &lt;em&gt;told&lt;/em&gt; me that Brad is an addict, and I don&amp;#8217;t think I would&amp;#8217;ve cared too much.  An addiction to a fantasy drug in a wasteland doesn&amp;#8217;t &lt;em&gt;mean&lt;/em&gt; anything to me, especially about this tiny sprite man I just met, so I would&amp;#8217;ve filed this away as a sterile fact and forgotten about it.  By making his addiction affect &lt;strong&gt;me&lt;/strong&gt;, I&amp;#8217;m now invested in it.  I wish Brad &lt;em&gt;weren&amp;#8217;t&lt;/em&gt; addicted, even if only because it&amp;#8217;s annoying.  I found a party member once who turned out to have the same addiction, and I felt dread just from seeing the icon for the status effect.  I&amp;#8217;ve been looped into the events of this story through the medium I use to interact with it: the &lt;em&gt;game&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;It&amp;#8217;s a really good use of games as a medium.  Even before I&amp;#8217;m invested in the characters, I&amp;#8217;m invested in what&amp;#8217;s happening to them, because it impacts the&amp;nbsp;game!&lt;/p&gt;
&lt;p&gt;Incidentally, you can get Joy as an item, which will temporarily cure your withdrawal…  but you mostly find it by looting the corpses of grotesque mutant flesh horrors you encounter.  I don&amp;#8217;t think the game would have the player abruptly mutate out of nowhere, but I wasn&amp;#8217;t about to find out, either.  We never took&amp;nbsp;any.&lt;/p&gt;
&lt;hr /&gt;
&lt;p&gt;Virtually every staple of the &lt;span class="caps"&gt;RPG&lt;/span&gt; genre has been played with in some way to tie it into the theme/setting.  I love it, and I think it works so well precisely because it plays with expectations of how RPGs usually&amp;nbsp;work.&lt;/p&gt;
&lt;p&gt;Most obviously, the game is a sidescroller, not top-down.  You can&amp;#8217;t jump freely, but you can hop onto one-tile-high boxes and climb ropes.  You can also drop off off ledges…  but your entire party will take &lt;em&gt;fall damage&lt;/em&gt;, which gets rapidly more severe the further you&amp;nbsp;fall.&lt;/p&gt;
&lt;p&gt;This wouldn&amp;#8217;t be too much of a problem, except that healing is hard to come by for most of the game.  Several hub areas have campfires you can sleep next to to restore all your health and &lt;span class="caps"&gt;MP&lt;/span&gt;, but when you wake up, &lt;em&gt;something&lt;/em&gt; will have happened to you.  Maybe just a weird cutscene, or maybe one of your party members has decided to leave&amp;nbsp;permanently.&lt;/p&gt;
&lt;p&gt;Okay, so use healing items instead?  Good luck; money is also hard to come by, and honestly so are shops, &lt;em&gt;and&lt;/em&gt; many of the healing items are woefully&amp;nbsp;underpowered.&lt;/p&gt;
&lt;p&gt;Grind for money?  Good luck there, too!  While the game has plenty of battles, virtually every enemy is a unique overworld human who only appears once, and then is dead, because you killed him.  Only a handful of places have unlimited random encounters, and grinding is not especially&amp;nbsp;pleasant.&lt;/p&gt;
&lt;p&gt;The &amp;#8220;best&amp;#8221; way to get a reliable heal is to savescum — save the game, sleep by the campfire, and reload if you don&amp;#8217;t like what you wake up&amp;nbsp;to.&lt;/p&gt;
&lt;p&gt;In a similar vein, there&amp;#8217;s a part of the game where you&amp;#8217;re forced to play Russian Roulette.  You choose a party member; he and an opponent will take turns shooting themselves in the head until someone finds a loaded chamber.  If your party member loses, &lt;strong&gt;he is dead&lt;/strong&gt;.  And you have to keep playing until you &lt;em&gt;win&lt;/em&gt; three times, so there&amp;#8217;s no upper limit on how many people you might lose.  I couldn&amp;#8217;t find any way to influence who won, so I just had to savescum for a good half hour until I made it through with minimal&amp;nbsp;losses.&lt;/p&gt;
&lt;p&gt;It was &lt;em&gt;maddening&lt;/em&gt;, but also a really good idea.  Games don&amp;#8217;t often incorporate the existence of saves &lt;em&gt;into&lt;/em&gt; the gameplay, and when they do, they usually break the fourth wall and get all meta about it.  Saves are never acknowledged in-universe here (aside from the existence of save points), but surely these parts of the game were designed &lt;em&gt;knowing&lt;/em&gt; that the best way through them is by reloading.  It&amp;#8217;s rarely done, it can easily feel unfair, and it drove me up the wall — but it was certainly painful, as intended, and I kinda love&amp;nbsp;that.&lt;/p&gt;
&lt;p&gt;(Naturally, I&amp;#8217;m told there&amp;#8217;s a hard mode, where you can only use each save point &lt;em&gt;once&lt;/em&gt;.)&lt;/p&gt;
&lt;p&gt;The game also drives home the finality of death much better than most.  It&amp;#8217;s not hard to overlook the death of a redshirt, a character with a bit part who simply doesn&amp;#8217;t appear any more.  This game permanently kills your &lt;em&gt;party members&lt;/em&gt;.  Russian Roulette isn&amp;#8217;t even the only way you can lose them!  Multiple cutscenes force you to choose between losing a life or some other drastic consequence.  (Even better, you can try to fight the person forcing this choice on you, and he will decimate you.)  As the game progresses, you start to encounter enemies who can simply one-shot murder your party&amp;nbsp;members.&lt;/p&gt;
&lt;p&gt;It&amp;#8217;s such a great angle.  Just like with Brad&amp;#8217;s withdrawal, you don&amp;#8217;t want to avoid their deaths because it&amp;#8217;d be &lt;em&gt;emotional&lt;/em&gt; — there are dozens of party members you can recruit (though we only found a fraction of them), and most of them you only know a paragraph about — but because it would &lt;em&gt;inconvenience you personally&lt;/em&gt;.  Chances are, you have your strongest dudes in your party at any given time, so losing one of them &lt;em&gt;sucks&lt;/em&gt;.  And with few random encounters, you can&amp;#8217;t just grind someone else up to an appropriate level; it feels like there&amp;#8217;s a finite amount of &lt;span class="caps"&gt;XP&lt;/span&gt; in the game, and if someone high-level dies, you&amp;#8217;ve &lt;em&gt;lost&lt;/em&gt; all the &lt;span class="caps"&gt;XP&lt;/span&gt; that went into&amp;nbsp;them.&lt;/p&gt;
&lt;hr /&gt;
&lt;p&gt;The battles themselves are fairly straightforward.  You can attack normally or use a special move that costs &lt;span class="caps"&gt;MP&lt;/span&gt;.  &lt;span class="caps"&gt;SP&lt;/span&gt;?  Some kind of&amp;nbsp;points.&lt;/p&gt;
&lt;p&gt;Two things in particular stand out.  One I mentioned above: the vast majority of the encounters are one-time affairs against distinct named NPCs, who you then never see again, because they are dead, because you killed&amp;nbsp;them.&lt;/p&gt;
&lt;p&gt;The other is the somewhat unusual set of status effects.  The staples like poison and sleep are here, but don&amp;#8217;t show up all that often; more frequent are statuses like &lt;em&gt;weird&lt;/em&gt;, &lt;em&gt;drunk&lt;/em&gt;, &lt;em&gt;stink&lt;/em&gt;, or &lt;em&gt;cool&lt;/em&gt;.  If you do take Joy (which also cures &lt;em&gt;depression&lt;/em&gt;), you become &lt;em&gt;joyed&lt;/em&gt; for a short&amp;nbsp;time.&lt;/p&gt;
&lt;p&gt;The game plays with these in a few neat ways, besides just Brad&amp;#8217;s withdrawal.  Some party members have a status like &lt;em&gt;stink&lt;/em&gt; or &lt;em&gt;cool&lt;/em&gt; permanently.  Some battles are against people who don&amp;#8217;t want to fight at all — and so they&amp;#8217;ll spend most of the battle &lt;em&gt;crying&lt;/em&gt;, purely for flavor impact.  Seeing that for the first time hit me pretty hard; until then we&amp;#8217;d only seen crying as a &lt;em&gt;mechanical&lt;/em&gt; side effect of having sand kicked in one&amp;#8217;s&amp;nbsp;face.&lt;/p&gt;
&lt;hr /&gt;
&lt;p&gt;The game does drag on a &lt;em&gt;bit&lt;/em&gt;.  I think we poured 10 in-game hours into it, which doesn&amp;#8217;t count time spent reloading.  It doesn&amp;#8217;t help that you walk not super&amp;nbsp;fast.&lt;/p&gt;
&lt;p&gt;My biggest problem was with getting my bearings; I&amp;#8217;m sure we spent a lot of that time wandering around accomplishing nothing.  Most of the world is focused around one of a few hub areas, and once you&amp;#8217;ve completed one hub, you can move onto the next one.  That&amp;#8217;s fine.  Trouble is, you can go any of a dozen different directions from each hub, and most of those directions will lead you to very similar-looking hills built out of the same tiny handful of tiles.  The connections between places are mostly cave entrances, which also largely look the same.  Combine that with needing to backtrack for puzzle or progression reasons, and it&amp;#8217;s incredibly difficult to keep track of where you&amp;#8217;ve been, what you&amp;#8217;ve done, and where you need to go&amp;nbsp;next.&lt;/p&gt;
&lt;p&gt;I don&amp;#8217;t know that the game is &lt;em&gt;wrong&lt;/em&gt; here; the aesthetic and world layout are fantastic at conveying a desolate wasteland.  I wouldn&amp;#8217;t even be surprised if the navigation were deliberately designed this way.  (On the other hand, assuming every annoyance in a despair-ridden game is deliberate might be giving it too much credit.)  But damn it&amp;#8217;s still&amp;nbsp;frustrating.&lt;/p&gt;
&lt;p&gt;I felt a little lost in the battle system, too.  Towards the end of the game, Brad in particular had over a dozen skills he could use, but I still couldn&amp;#8217;t confidently tell you which were the strongest.  New skills sometimes appear in the &lt;em&gt;middle&lt;/em&gt; of the list or cost less than previous skills, and the game doesn&amp;#8217;t outright tell you how much damage any of them do.  I know this is the &amp;#8220;classic &lt;span class="caps"&gt;RPG&lt;/span&gt;&amp;#8221; style, and I don&amp;#8217;t think it was hugely inconvenient, but it feels weird to barely know how my own skills work.  I think this puts me off getting into new RPGs, just generally; there&amp;#8217;s a whole new set of things I have to learn about, and games in this style often won&amp;#8217;t just &lt;em&gt;tell&lt;/em&gt; me anything, so there&amp;#8217;s this whole separate meta-puzzle to figure out before I can play the actual game&amp;nbsp;effectively.&lt;/p&gt;
&lt;p&gt;Also, the sound could use a &lt;em&gt;little&lt;/em&gt; bit of…  mastering?  Some music and sound effects are significantly louder and screechier than others.  Painful, you could&amp;nbsp;say.&lt;/p&gt;
&lt;hr /&gt;
&lt;p&gt;The world is full of side characters with their own stuff going on, which is also something I love seeing in games; too often, the whole world feels like an obstacle course specifically designed for&amp;nbsp;you.&lt;/p&gt;
&lt;p&gt;Also, many of those characters are, well, not great people.  Really, most of the game is kinda fucked up.  Consider: the &lt;em&gt;weird&lt;/em&gt; status effect is most commonly inflicted by the &amp;#8220;Grope&amp;#8221; skill.  It makes you feel weird, you see.  Oh, and the currency is porn&amp;nbsp;magazines.&lt;/p&gt;
&lt;p&gt;And then there are the gangs, the various spins on sex clubs, the forceful drug kingpins, and the overall violence that permeates everything (you stumble upon an alarming number of corpses).  The game neither condones nor condemns any of this; it simply offers some ideas of how people might behave at the end of the world.  It&amp;#8217;s certainly the grittiest interpretation I&amp;#8217;ve&amp;nbsp;seen.&lt;/p&gt;
&lt;p&gt;I don&amp;#8217;t usually like post-apocalypses, because they try to have these very hopeful stories, but then at the end the world is still a blighted hellscape so what was the point of any of that?  I like this game much better for being a blighted hellscape throughout.  The story is worth following to see where it goes, not just because you expect everything wrapped up neatly at the&amp;nbsp;end.&lt;/p&gt;
&lt;p&gt;…I realize I&amp;#8217;ve made this game sound monumentally depressing throughout, but it manages to pack in a lot of funny moments as well, from the subtle to the overt.  In retrospect, it&amp;#8217;s actually &lt;em&gt;really good&lt;/em&gt; at balancing the mood so it doesn&amp;#8217;t get &lt;em&gt;too&lt;/em&gt; depressing.  If nothing else, it&amp;#8217;s hilarious to watch this gruff, solemn, battle-scarred, middle-aged man pedal around on a kid&amp;#8217;s bike he&amp;nbsp;found.&lt;/p&gt;
&lt;hr /&gt;
&lt;p&gt;An obvious theme of the game is &lt;em&gt;despair&lt;/em&gt;, but the more I think about it, the more I wonder if &lt;em&gt;ambiguity&lt;/em&gt; is a theme as well.  It certainly fits the confusing&amp;nbsp;geography.&lt;/p&gt;
&lt;p&gt;Even the premise is a little ambiguous.  Is/was Olathe a city, a country, a whole planet?  Did the apocalypse affect only Olathe, or the whole world?  Does it matter in an &lt;span class="caps"&gt;RPG&lt;/span&gt;, where the only world that exists is the one mapped out within the&amp;nbsp;game?&lt;/p&gt;
&lt;p&gt;Towards the end of the game, you catch up with Buddy, but she &lt;em&gt;rejects&lt;/em&gt; you, apparently resentful that you kept her hidden away for her entire life.  Brad presses on anyway, insisting on protecting&amp;nbsp;her.&lt;/p&gt;
&lt;p&gt;At that point I wasn&amp;#8217;t sure I was still on Brad&amp;#8217;s side.  But he&amp;#8217;s not &lt;em&gt;wrong&lt;/em&gt;, either.  Is he?  Maybe it depends on how old Buddy is — but the game never tells us.  Her sprite is a bit smaller than the men&amp;#8217;s, but it&amp;#8217;s hard to gauge much from small exaggerated sprites, and she might just be shorter.  In the beginning of the game, she was doing kid-like drawings, but we don&amp;#8217;t know how much time passed after that.  Everyone seems to take for granted that she&amp;#8217;s capable of bearing children, and she &lt;em&gt;talks&lt;/em&gt; like an adult.  So is she old enough to be making this decision, or young enough for parent figure Brad to overrule her?  What &lt;em&gt;is&lt;/em&gt; the appropriate age of agency, anyway, when you&amp;#8217;re the last girl/woman left more than a decade after the end of the&amp;nbsp;world?&lt;/p&gt;
&lt;p&gt;Can you repopulate a species with only &lt;em&gt;one&lt;/em&gt; woman,&amp;nbsp;anyway?&lt;/p&gt;
&lt;hr /&gt;
&lt;p&gt;Well, that went on a bit longer than I intended.  This game has a lot of small touches that stood out to me, and they all wove together very&amp;nbsp;well.&lt;/p&gt;
&lt;p&gt;Should you play it?  I have absolutely no&amp;nbsp;idea.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;&lt;span class="caps"&gt;FINAL&lt;/span&gt; &lt;span class="caps"&gt;SCORE&lt;/span&gt;:&lt;/strong&gt; 1 out of 6&amp;nbsp;chambers&lt;/p&gt;
&lt;h2 id="lisa-the-joyful"&gt;&lt;a class="toclink" href="#lisa-the-joyful"&gt;Lisa: The Joyful&lt;/a&gt;&lt;/h2&gt;
&lt;div class="prose-full-illustration"&gt;
&lt;img src="https://eev.ee/media/game-night/lisa-the-joyful.png" alt=""&gt;
&lt;/div&gt;

&lt;p&gt;&lt;center&gt;&lt;em&gt;fairly short · classic rpg · aug 2015 · lin/mac/win · $5 on &lt;a href="https://dingalingboy.itch.io/lisa-the-joyful-ost"&gt;itch&lt;/a&gt; or &lt;a href="http://store.steampowered.com/app/379310/LISA_the_Joyful/"&gt;steam&lt;/a&gt;&lt;/em&gt;&lt;/center&gt;&lt;/p&gt;
&lt;p&gt;Surprise!  There&amp;#8217;s a third game to round out this&amp;nbsp;trilogy.&lt;/p&gt;
&lt;p&gt;Lisa: The Joyful is &lt;em&gt;much&lt;/em&gt; shorter, maybe three hours long — enough to be played in a night rather than over the better part of a&amp;nbsp;week.&lt;/p&gt;
&lt;p&gt;This one picks up immediately after the end of Painful, with you now playing as Buddy.  It takes a drastic turn early on: Buddy decides that, rather than hide from the world, she must &lt;strong&gt;conquer&lt;/strong&gt; it.  She sets out to murder all the big bosses and become&amp;nbsp;queen.&lt;/p&gt;
&lt;p&gt;The battle system has been inherited from the previous game, but battles are much more straightforward this time around.  You can&amp;#8217;t recruit any party members; for much of the game, it&amp;#8217;s just you and a&amp;nbsp;sword.&lt;/p&gt;
&lt;p&gt;There is a catch!  Of&amp;nbsp;course.&lt;/p&gt;
&lt;p&gt;The catch is that you &lt;em&gt;do not have&lt;/em&gt; enough health to survive most boss battles without healing.  With no party members, you cannot heal via skills.  I don&amp;#8217;t think you could buy healing items anywhere, either.  You have a few when the game begins, but once you run out, that&amp;#8217;s&amp;nbsp;it.&lt;/p&gt;
&lt;p&gt;Except…  you also have…  some Joy.  Which restores you to full health and also makes you crit with every hit.  And drops off of several&amp;nbsp;enemies.&lt;/p&gt;
&lt;p&gt;We didn&amp;#8217;t even recognize Joy as a healing item at first, since we never used it in Painful; it&amp;#8217;s description simply says that it makes you feel nothing, and we&amp;#8217;d assumed the whole point of it was to stave off withdrawal, which Buddy doesn&amp;#8217;t experience.  Luckily, the game provided a hint in the form of an &lt;span class="caps"&gt;NPC&lt;/span&gt; who offers to switch on easy&amp;nbsp;mode:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;What&amp;#8217;s that? Bad guys too tough? Not enough jerky? You don&amp;#8217;t want to take Joy!? Say no more, you&amp;#8217;ve come to the right&amp;nbsp;place!&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;So the game is aware that it&amp;#8217;s unfairly difficult, and it&amp;#8217;s deliberately &lt;em&gt;forcing&lt;/em&gt; you to take Joy, and it is in fact entirely constructed around this concept.  I guess the title is a pretty good hint,&amp;nbsp;too.&lt;/p&gt;
&lt;p&gt;I don&amp;#8217;t feel quite as strongly about Joyful as I do about Painful.  (Admittedly, I was really tired and starting to doze off towards the end of Joyful.)  Once you get that the gimmick is to force you to use Joy, the game basically reduces to a moderate-difficulty boss rush.  Other than that, the only thing that stood out to me mechanically was that Buddy learns a skill where she lifts her shirt to inflict &lt;em&gt;flustered&lt;/em&gt; as a status effect — kind of a lingering echo of how outrageous the previous game could&amp;nbsp;be.&lt;/p&gt;
&lt;p&gt;You do get a healthy serving of plot, which is nice and ties a few things together.  I wouldn&amp;#8217;t say it exactly wraps up the story, but it doesn&amp;#8217;t feel like it&amp;#8217;s missing anything either; it&amp;#8217;s exactly as murky as you&amp;#8217;d&amp;nbsp;expect.&lt;/p&gt;
&lt;p&gt;I think it&amp;#8217;s worth playing Joyful if you&amp;#8217;ve played Painful.  It just didn&amp;#8217;t have the same impact on me.  It probably doesn&amp;#8217;t help that I &lt;em&gt;don&amp;#8217;t like&lt;/em&gt; Buddy as a person.  She seems cold, violent, and cruel.  Appropriate for the world and a product of her environment, I&amp;nbsp;suppose.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;&lt;span class="caps"&gt;FINAL&lt;/span&gt; &lt;span class="caps"&gt;SCORE&lt;/span&gt;:&lt;/strong&gt; 300&amp;nbsp;Mags&lt;/p&gt;
&lt;h2 id="moop"&gt;&lt;a class="toclink" href="#moop"&gt;MOOP&lt;/a&gt;&lt;/h2&gt;
&lt;div class="prose-full-illustration"&gt;
&lt;img src="https://eev.ee/media/game-night/moop.png" alt=""&gt;
&lt;/div&gt;

&lt;p&gt;&lt;center&gt;&lt;em&gt;fairly short · inventory game · nov 2017 · win · free on &lt;a href="https://geoff-moore.itch.io/moop"&gt;itch&lt;/a&gt;&lt;/em&gt;&lt;/center&gt;&lt;/p&gt;
&lt;p&gt;Finally, as something of a palate cleanser, we have &lt;span class="caps"&gt;MOOP&lt;/span&gt;: a delightful and charming little inventory&amp;nbsp;game.&lt;/p&gt;
&lt;p&gt;I don&amp;#8217;t think &amp;#8220;inventory game&amp;#8221; is a real genre, but I mean the kind of game where you go around collecting items and using them in the right place.  Puzzle-driven, but with &amp;#8220;puzzles&amp;#8221; that can largely be solved by simply trying everything everywhere.  I&amp;#8217;d put a lot of point and click adventures in the same category, despite having a radically different interface.  Is that fair?  Yes, because it&amp;#8217;s my&amp;nbsp;blog.&lt;/p&gt;
&lt;p&gt;&lt;span class="caps"&gt;MOOP&lt;/span&gt; was almost certainly also made in &lt;span class="caps"&gt;RPG&lt;/span&gt; Maker, but it breaks the mold in a very different way by not being an &lt;span class="caps"&gt;RPG&lt;/span&gt;.  There are no battles whatsoever, only interactions on the overworld; you progress solely via dialogue and puzzle-solving.  Examining something gives you a short menu of verbs — use, talk, get — reminiscent of interactive fiction, or perhaps the graphical &amp;#8220;adventure&amp;#8221; games that took inspiration from interactive fiction.  (God, &amp;#8220;adventure game&amp;#8221; is the worst phrase.  Every game is an adventure!  It doesn&amp;#8217;t mean&amp;nbsp;anything!)&lt;/p&gt;
&lt;p&gt;Everything about the game is extremely chill.  I love the monochrome aesthetic combined with a large screen resolution; it feels like I&amp;#8217;m peeking into an alternate universe where the Game Boy got bigger but never gained color.  I played halfway through the game before realizing that the protagonist (Moop) doesn&amp;#8217;t have a walk animation; they simply slide around.  Somehow, it&amp;nbsp;works.&lt;/p&gt;
&lt;p&gt;The puzzles are a little clever, yet low-pressure; the world is small enough that you can examine everything again if you get stuck, and there&amp;#8217;s no way to lose or be set back.  The music is lovely, too.  It just feels good to wander around in a world that manages to make sepia look very&amp;nbsp;pretty.&lt;/p&gt;
&lt;p&gt;The story manages to pack a lot into a very short time.  It&amp;#8217;s…  gosh, I don&amp;#8217;t know.  It has a very distinct texture to it that I&amp;#8217;m not sure I&amp;#8217;ve seen before.  The plot weaves through several major events that each have very different moods, and it moves very quickly — but it&amp;#8217;s well-written and doesn&amp;#8217;t feel rushed or disjoint.  It&amp;#8217;s lighthearted, but takes itself seriously enough for me to get invested.  It&amp;#8217;s fucking&amp;nbsp;witchcraft.&lt;/p&gt;
&lt;p&gt;I think there was even a non-binary character!  Just kinda nonchalantly in there.&amp;nbsp;Awesome.&lt;/p&gt;
&lt;p&gt;What a happy, charming game.  Play if you would like to be happy and&amp;nbsp;charmed.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;&lt;span class="caps"&gt;FINAL&lt;/span&gt; &lt;span class="caps"&gt;SCORE&lt;/span&gt;:&lt;/strong&gt; 1 waxing&amp;nbsp;moon&lt;/p&gt;</content><category term="articles"></category></entry><entry><title>Object models</title><link href="https://eev.ee/blog/2017/11/28/object-models/" rel="alternate"></link><published>2017-11-28T21:42:00-08:00</published><updated>2017-11-28T21:42:00-08:00</updated><author><name>Eevee</name></author><id>tag:eev.ee,2017-11-28:/blog/2017/11/28/object-models/</id><summary type="html">&lt;p&gt;Anonymous asks, with dollars:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;More about programming languages!&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Well then!&lt;/p&gt;
&lt;p&gt;I’ve written before about &lt;a href="https://eev.ee/blog/2013/03/03/the-controller-pattern-is-awful-and-other-oo-heresy/"&gt;what I think objects &lt;em&gt;are&lt;/em&gt;&lt;/a&gt;: state and behavior, which in practice mostly means &lt;em&gt;method calls&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;I suspect that the popular impression of what objects &lt;em&gt;are&lt;/em&gt;, and also how they should &lt;em&gt;work&lt;/em&gt;, comes from whatever C++ and Java happen to do.  From that point of view, the whole post above is probably nonsense.  If the baseline notion of “object” is a rigid definition woven tightly into the design of two massively popular languages, then it doesn’t even make sense to talk about what “object” &lt;em&gt;should&lt;/em&gt; mean — it &lt;em&gt;does&lt;/em&gt; mean the features of those languages, and cannot possibly mean anything else.&lt;/p&gt;
&lt;p&gt;I think that’s a shame!  It piles a lot of baggage onto a fairly simple idea.  Polymorphism, for example, has nothing to do with &lt;em&gt;objects&lt;/em&gt; — it’s an escape hatch for &lt;em&gt;static type systems&lt;/em&gt;.  Inheritance isn’t the only way to reuse code between objects, but it’s the easiest and fastest one, so it’s what we get.  Frankly, it’s much closer to a speed tradeoff than a fundamental part of the concept.&lt;/p&gt;
&lt;p&gt;We could do with more experimentation around how objects work, but that’s impossible in the languages most commonly thought of as object-oriented.&lt;/p&gt;
&lt;p&gt;Here, then, is a (very) brief run through the inner workings of objects in four very dynamic languages.  I don’t think I really appreciated objects until I’d spent some time with Python, and I hope this can help someone else whet their own appetite.&lt;/p&gt;
</summary><content type="html">&lt;p&gt;Anonymous asks, with&amp;nbsp;dollars:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;More about programming&amp;nbsp;languages!&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Well&amp;nbsp;then!&lt;/p&gt;
&lt;p&gt;I&amp;#8217;ve written before about &lt;a href="https://eev.ee/blog/2013/03/03/the-controller-pattern-is-awful-and-other-oo-heresy/"&gt;what I think objects &lt;em&gt;are&lt;/em&gt;&lt;/a&gt;: state and behavior, which in practice mostly means &lt;em&gt;method calls&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;I suspect that the popular impression of what objects &lt;em&gt;are&lt;/em&gt;, and also how they should &lt;em&gt;work&lt;/em&gt;, comes from whatever C++ and Java happen to do.  From that point of view, the whole post above is probably nonsense.  If the baseline notion of &amp;#8220;object&amp;#8221; is a rigid definition woven tightly into the design of two massively popular languages, then it doesn&amp;#8217;t even make sense to talk about what &amp;#8220;object&amp;#8221; &lt;em&gt;should&lt;/em&gt; mean — it &lt;em&gt;does&lt;/em&gt; mean the features of those languages, and cannot possibly mean anything&amp;nbsp;else.&lt;/p&gt;
&lt;p&gt;I think that&amp;#8217;s a shame!  It piles a lot of baggage onto a fairly simple idea.  Polymorphism, for example, has nothing to do with &lt;em&gt;objects&lt;/em&gt; — it&amp;#8217;s an escape hatch for &lt;em&gt;static type systems&lt;/em&gt;.  Inheritance isn&amp;#8217;t the only way to reuse code between objects, but it&amp;#8217;s the easiest and fastest one, so it&amp;#8217;s what we get.  Frankly, it&amp;#8217;s much closer to a speed tradeoff than a fundamental part of the&amp;nbsp;concept.&lt;/p&gt;
&lt;p&gt;We could do with more experimentation around how objects work, but that&amp;#8217;s impossible in the languages most commonly thought of as&amp;nbsp;object-oriented.&lt;/p&gt;
&lt;p&gt;Here, then, is a (very) brief run through the inner workings of objects in four very dynamic languages.  I don&amp;#8217;t think I really appreciated objects until I&amp;#8217;d spent some time with Python, and I hope this can help someone else whet their own&amp;nbsp;appetite.&lt;/p&gt;


&lt;h2 id="python-3"&gt;&lt;a class="toclink" href="#python-3"&gt;Python 3&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Of the four languages I&amp;#8217;m going to touch on, Python will look the most familiar to the Java and C++ crowd.  For starters, it actually has&amp;nbsp;a &lt;code&gt;class&lt;/code&gt; construct.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="linenos"&gt; 1&lt;/span&gt;&lt;span class="k"&gt;class&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nc"&gt;Vector&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="linenos"&gt; 2&lt;/span&gt;    &lt;span class="k"&gt;def&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="fm"&gt;__init__&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;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="linenos"&gt; 3&lt;/span&gt;        &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&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="linenos"&gt; 4&lt;/span&gt;        &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&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="linenos"&gt; 5&lt;/span&gt;
&lt;span class="linenos"&gt; 6&lt;/span&gt;    &lt;span class="k"&gt;def&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="fm"&gt;__neg__&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="linenos"&gt; 7&lt;/span&gt;        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;Vector&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;y&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="linenos"&gt; 8&lt;/span&gt;
&lt;span class="linenos"&gt; 9&lt;/span&gt;    &lt;span class="k"&gt;def&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;__div__&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;denom&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
&lt;span class="linenos"&gt;10&lt;/span&gt;        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;Vector&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&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;denom&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&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;denom&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="linenos"&gt;11&lt;/span&gt;
&lt;span class="linenos"&gt;12&lt;/span&gt;    &lt;span class="nd"&gt;@property&lt;/span&gt;
&lt;span class="linenos"&gt;13&lt;/span&gt;    &lt;span class="k"&gt;def&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;magnitude&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="linenos"&gt;14&lt;/span&gt;        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&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;2&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&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;2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;**&lt;/span&gt; &lt;span class="mf"&gt;0.5&lt;/span&gt;
&lt;span class="linenos"&gt;15&lt;/span&gt;
&lt;span class="linenos"&gt;16&lt;/span&gt;    &lt;span class="k"&gt;def&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;normalized&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="linenos"&gt;17&lt;/span&gt;        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;magnitude&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;The &lt;code&gt;__init__&lt;/code&gt; method is an &lt;em&gt;initializer&lt;/em&gt;, which is like a constructor but named differently (because the object already exists in a usable form by the time the initializer is called).  Operator overloading is done by implementing methods with other&amp;nbsp;special &lt;code&gt;__dunder__&lt;/code&gt; names.  Properties can be created&amp;nbsp;with &lt;code&gt;@property&lt;/code&gt;, where&amp;nbsp;the &lt;code&gt;@&lt;/code&gt; is syntax for applying a wrapper function to a function as it&amp;#8217;s defined.  You can do inheritance, even&amp;nbsp;multiply:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="linenos"&gt;1&lt;/span&gt;&lt;span class="k"&gt;class&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nc"&gt;Foo&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;B&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;C&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
&lt;span class="linenos"&gt;2&lt;/span&gt;    &lt;span class="k"&gt;def&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;bar&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;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;z&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
&lt;span class="linenos"&gt;3&lt;/span&gt;        &lt;span class="c1"&gt;# do some stuff&lt;/span&gt;
&lt;span class="linenos"&gt;4&lt;/span&gt;        &lt;span class="nb"&gt;super&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;bar&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;z&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Cool, a very traditional object&amp;nbsp;model.&lt;/p&gt;
&lt;p&gt;Except&amp;#8230;  for some&amp;nbsp;details.&lt;/p&gt;
&lt;h3 id="some-details"&gt;&lt;a class="toclink" href="#some-details"&gt;Some details&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;For one, Python objects don&amp;#8217;t have a fixed layout.  Code both inside and outside the class can add &lt;em&gt;or remove&lt;/em&gt; whatever attributes they want from whatever object they want.  The underlying storage is just&amp;nbsp;a &lt;code&gt;dict&lt;/code&gt;, Python&amp;#8217;s mapping type.  (Or, rather, something like one.  Also, it&amp;#8217;s possible to change, which will probably be the case for everything I say&amp;nbsp;here.)&lt;/p&gt;
&lt;p&gt;If you create some attributes at the class level, you&amp;#8217;ll start to get a peek behind the&amp;nbsp;curtains:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="linenos"&gt; 1&lt;/span&gt;&lt;span class="k"&gt;class&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nc"&gt;Foo&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="linenos"&gt; 2&lt;/span&gt;    &lt;span class="n"&gt;values&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;
&lt;span class="linenos"&gt; 3&lt;/span&gt;
&lt;span class="linenos"&gt; 4&lt;/span&gt;    &lt;span class="k"&gt;def&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;add_value&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;value&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
&lt;span class="linenos"&gt; 5&lt;/span&gt;        &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;values&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;value&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="linenos"&gt; 6&lt;/span&gt;
&lt;span class="linenos"&gt; 7&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;Foo&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="linenos"&gt; 8&lt;/span&gt;&lt;span class="n"&gt;b&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Foo&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="linenos"&gt; 9&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;add_value&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;a&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="linenos"&gt;10&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;a&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;values&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;  &lt;span class="c1"&gt;# [&amp;#39;a&amp;#39;]&lt;/span&gt;
&lt;span class="linenos"&gt;11&lt;/span&gt;&lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;add_value&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;b&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="linenos"&gt;12&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;b&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;values&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;  &lt;span class="c1"&gt;# [&amp;#39;a&amp;#39;, &amp;#39;b&amp;#39;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;The &lt;code&gt;[]&lt;/code&gt; assigned&amp;nbsp;to &lt;code&gt;values&lt;/code&gt; isn&amp;#8217;t a &lt;em&gt;default&lt;/em&gt; assigned to each object.  In fact, the individual objects don&amp;#8217;t know about it at all!  You can&amp;nbsp;use &lt;code&gt;vars(a)&lt;/code&gt; to get at the underlying&amp;nbsp;storage &lt;code&gt;dict&lt;/code&gt;, and you won&amp;#8217;t see&amp;nbsp;a &lt;code&gt;values&lt;/code&gt; entry in there&amp;nbsp;anywhere.&lt;/p&gt;
&lt;p&gt;Instead, &lt;code&gt;values&lt;/code&gt; lives on the &lt;em&gt;class&lt;/em&gt;, which is a value (and thus an object) in its own right.  When Python is asked&amp;nbsp;for &lt;code&gt;self.values&lt;/code&gt;, it checks to see&amp;nbsp;if &lt;code&gt;self&lt;/code&gt; has&amp;nbsp;a &lt;code&gt;values&lt;/code&gt; attribute; in this case, it doesn&amp;#8217;t, so Python &lt;em&gt;keeps going&lt;/em&gt; and asks the &lt;em&gt;class&lt;/em&gt; for&amp;nbsp;one.&lt;/p&gt;
&lt;p&gt;Python&amp;#8217;s object model is secretly &lt;strong&gt;prototypical&lt;/strong&gt; — a class acts as a prototype, as a shared set of fallback values, for its&amp;nbsp;objects.&lt;/p&gt;
&lt;p&gt;In fact, this is also how method calls work!  They aren&amp;#8217;t syntactically special at all, which you can see by separating the attribute lookup from the&amp;nbsp;call.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="linenos"&gt;1&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;abc&amp;quot;&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;startswith&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;a&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;  &lt;span class="c1"&gt;# True&lt;/span&gt;
&lt;span class="linenos"&gt;2&lt;/span&gt;&lt;span class="n"&gt;meth&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;abc&amp;quot;&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;startswith&lt;/span&gt;
&lt;span class="linenos"&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;meth&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;a&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;  &lt;span class="c1"&gt;# True&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Reading &lt;code&gt;obj.method&lt;/code&gt; looks for&amp;nbsp;a &lt;code&gt;method&lt;/code&gt; attribute; if there isn&amp;#8217;t one&amp;nbsp;on &lt;code&gt;obj&lt;/code&gt;, Python checks the class.  Here, it finds one: it&amp;#8217;s a function from the class&amp;nbsp;body.&lt;/p&gt;
&lt;p&gt;Ah, but wait!  In the code I just&amp;nbsp;showed, &lt;code&gt;meth&lt;/code&gt; seems to &amp;#8220;know&amp;#8221; the object it came from, so it can&amp;#8217;t just be a plain function.  If you inspect the resulting value, it claims to be a &amp;#8220;bound method&amp;#8221; or &amp;#8220;built-in method&amp;#8221; rather than a function, too.  Something funny is going on here, and that funny something is the &lt;em&gt;descriptor protocol&lt;/em&gt;.&lt;/p&gt;
&lt;h3 id="descriptors"&gt;&lt;a class="toclink" href="#descriptors"&gt;Descriptors&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Python allows attributes to implement their own custom behavior when read from or written to.  Such an attribute is called a &lt;em&gt;descriptor&lt;/em&gt;.  I&amp;#8217;ve &lt;a href="https://eev.ee/blog/2012/05/23/python-faq-descriptors/"&gt;written about them before&lt;/a&gt;, but here&amp;#8217;s a quick&amp;nbsp;overview.&lt;/p&gt;
&lt;p&gt;If Python looks up an attribute, finds it in a class, and the value it gets has&amp;nbsp;a &lt;code&gt;__get__&lt;/code&gt; method…  then &lt;em&gt;instead of&lt;/em&gt; using that value, Python will use the return value of&amp;nbsp;its &lt;code&gt;__get__&lt;/code&gt; method.&lt;/p&gt;
&lt;p&gt;The &lt;code&gt;@property&lt;/code&gt; decorator works this way.&amp;nbsp;The &lt;code&gt;magnitude&lt;/code&gt; property in my original example was shorthand for doing&amp;nbsp;this:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="linenos"&gt; 1&lt;/span&gt;&lt;span class="k"&gt;class&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nc"&gt;MagnitudeDescriptor&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="linenos"&gt; 2&lt;/span&gt;    &lt;span class="k"&gt;def&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="fm"&gt;__get__&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;instance&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;owner&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
&lt;span class="linenos"&gt; 3&lt;/span&gt;        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;instance&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="linenos"&gt; 4&lt;/span&gt;            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;
&lt;span class="linenos"&gt; 5&lt;/span&gt;        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;instance&lt;/span&gt;&lt;span class="o"&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;2&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;instance&lt;/span&gt;&lt;span class="o"&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;2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;**&lt;/span&gt; &lt;span class="mf"&gt;0.5&lt;/span&gt;
&lt;span class="linenos"&gt; 6&lt;/span&gt;
&lt;span class="linenos"&gt; 7&lt;/span&gt;&lt;span class="k"&gt;class&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nc"&gt;Vector&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="linenos"&gt; 8&lt;/span&gt;    &lt;span class="k"&gt;def&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="fm"&gt;__init__&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;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="linenos"&gt; 9&lt;/span&gt;        &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&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="linenos"&gt;10&lt;/span&gt;        &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&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="linenos"&gt;11&lt;/span&gt;
&lt;span class="linenos"&gt;12&lt;/span&gt;    &lt;span class="n"&gt;magnitude&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;MagnitudeDescriptor&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;When you ask&amp;nbsp;for &lt;code&gt;somevec.magnitude&lt;/code&gt;, Python&amp;nbsp;checks &lt;code&gt;somevec&lt;/code&gt; but doesn&amp;#8217;t&amp;nbsp;find &lt;code&gt;magnitude&lt;/code&gt;, so it consults the class instead.  The class does have&amp;nbsp;a &lt;code&gt;magnitude&lt;/code&gt;, and it&amp;#8217;s a value with&amp;nbsp;a &lt;code&gt;__get__&lt;/code&gt; method, so Python calls that method&amp;nbsp;and &lt;code&gt;somevec.magnitude&lt;/code&gt; evaluates to its return value.&amp;nbsp;(The &lt;code&gt;instance is None&lt;/code&gt; check is&amp;nbsp;because &lt;code&gt;__get__&lt;/code&gt; is called even if you get the descriptor directly from the class&amp;nbsp;via &lt;code&gt;Vector.magnitude&lt;/code&gt;.  A descriptor intended to work on instances can&amp;#8217;t do anything useful in that case, so the convention is to return the descriptor&amp;nbsp;itself.)&lt;/p&gt;
&lt;p&gt;You can also intercept attempts to write to or &lt;em&gt;delete&lt;/em&gt; an attribute, and do absolutely whatever you want instead.  But note that, similar to operating overloading in Python, the descriptor must be on a &lt;em&gt;class&lt;/em&gt;; you can&amp;#8217;t just slap one on an arbitrary object and have it&amp;nbsp;work.&lt;/p&gt;
&lt;p&gt;This brings me right around to how &amp;#8220;bound methods&amp;#8221; actually work.  Functions are descriptors!  The function type&amp;nbsp;implements &lt;code&gt;__get__&lt;/code&gt;, and when a function is retrieved from a class via an instance,&amp;nbsp;that &lt;code&gt;__get__&lt;/code&gt; bundles the function and the instance together into a tiny bound method object.  It&amp;#8217;s&amp;nbsp;essentially:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="linenos"&gt;1&lt;/span&gt;&lt;span class="k"&gt;class&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nc"&gt;FunctionType&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="linenos"&gt;2&lt;/span&gt;    &lt;span class="k"&gt;def&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="fm"&gt;__get__&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;instance&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;owner&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
&lt;span class="linenos"&gt;3&lt;/span&gt;        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;instance&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="linenos"&gt;4&lt;/span&gt;            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;
&lt;span class="linenos"&gt;5&lt;/span&gt;        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;functools&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;partial&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;instance&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;The &lt;code&gt;self&lt;/code&gt; passed as the first argument to methods is not special or magical in any way.  It&amp;#8217;s built out of a few simple pieces that are also readily accessible to Python&amp;nbsp;code.&lt;/p&gt;
&lt;p&gt;Note also that&amp;nbsp;because &lt;code&gt;obj.method()&lt;/code&gt; is just an attribute lookup and a call, Python doesn&amp;#8217;t actually care&amp;nbsp;whether &lt;code&gt;method&lt;/code&gt; is a method on the &lt;em&gt;class&lt;/em&gt; or just some callable thing on the &lt;em&gt;object&lt;/em&gt;.  You won&amp;#8217;t get the&amp;nbsp;auto-&lt;code&gt;self&lt;/code&gt; behavior if it&amp;#8217;s on the object, but otherwise there&amp;#8217;s no&amp;nbsp;difference.&lt;/p&gt;
&lt;h3 id="more-attribute-access-and-the-interesting-part"&gt;&lt;a class="toclink" href="#more-attribute-access-and-the-interesting-part"&gt;More attribute access, and the interesting part&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Descriptors are one of several ways to customize attribute access.  Classes can&amp;nbsp;implement &lt;code&gt;__getattr__&lt;/code&gt; to intervene when an attribute isn&amp;#8217;t found on an&amp;nbsp;object; &lt;code&gt;__setattr__&lt;/code&gt; and &lt;code&gt;__delattr__&lt;/code&gt; to intervene when any attribute is set or deleted;&amp;nbsp;and &lt;code&gt;__getattribute__&lt;/code&gt; to implement &lt;em&gt;unconditional&lt;/em&gt; attribute access.  (That last one is a fantastic way to create accidental recursion, since any attribute access you do&amp;nbsp;within &lt;code&gt;__getattribute__&lt;/code&gt; will of course&amp;nbsp;call &lt;code&gt;__getattribute__&lt;/code&gt; again.)&lt;/p&gt;
&lt;p&gt;Here&amp;#8217;s what I really love about Python.  It might seem like a magical special case that descriptors only work on classes, but it really isn&amp;#8217;t.  You could implement &lt;em&gt;exactly the same behavior yourself&lt;/em&gt;, in pure Python, using only the things I&amp;#8217;ve just told you about.  Classes are themselves objects, remember, and they are instances&amp;nbsp;of &lt;code&gt;type&lt;/code&gt;, so the reason descriptors only work on classes is&amp;nbsp;that &lt;code&gt;type&lt;/code&gt; effectively does&amp;nbsp;this:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="linenos"&gt; 1&lt;/span&gt;&lt;span class="k"&gt;class&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nc"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="linenos"&gt; 2&lt;/span&gt;    &lt;span class="k"&gt;def&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="fm"&gt;__getattribute__&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;name&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
&lt;span class="linenos"&gt; 3&lt;/span&gt;        &lt;span class="n"&gt;value&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;super&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="fm"&gt;__getattribute__&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="linenos"&gt; 4&lt;/span&gt;        &lt;span class="c1"&gt;# like all op overloads, __get__ must be on the type, not the instance&lt;/span&gt;
&lt;span class="linenos"&gt; 5&lt;/span&gt;        &lt;span class="n"&gt;ty&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;type&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="linenos"&gt; 6&lt;/span&gt;        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="nb"&gt;hasattr&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ty&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;__get__&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
&lt;span class="linenos"&gt; 7&lt;/span&gt;            &lt;span class="c1"&gt;# it&amp;#39;s a descriptor!  this is a class access so there is no instance&lt;/span&gt;
&lt;span class="linenos"&gt; 8&lt;/span&gt;            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;ty&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="fm"&gt;__get__&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kc"&gt;None&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="linenos"&gt; 9&lt;/span&gt;        &lt;span class="k"&gt;else&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="linenos"&gt;10&lt;/span&gt;            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;value&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;You can even trivially prove to yourself that this is what&amp;#8217;s going on by skipping&amp;nbsp;over &lt;code&gt;type&lt;/code&gt;&lt;span class="quo"&gt;&amp;#8216;&lt;/span&gt;s&amp;nbsp;behavior:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="linenos"&gt; 1&lt;/span&gt;&lt;span class="k"&gt;class&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nc"&gt;Descriptor&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="linenos"&gt; 2&lt;/span&gt;    &lt;span class="k"&gt;def&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="fm"&gt;__get__&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;instance&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;owner&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
&lt;span class="linenos"&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="s1"&gt;&amp;#39;called!&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="linenos"&gt; 4&lt;/span&gt;
&lt;span class="linenos"&gt; 5&lt;/span&gt;&lt;span class="k"&gt;class&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nc"&gt;Foo&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="linenos"&gt; 6&lt;/span&gt;    &lt;span class="n"&gt;bar&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Descriptor&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="linenos"&gt; 7&lt;/span&gt;
&lt;span class="linenos"&gt; 8&lt;/span&gt;&lt;span class="n"&gt;Foo&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;bar&lt;/span&gt;  &lt;span class="c1"&gt;# called!&lt;/span&gt;
&lt;span class="linenos"&gt; 9&lt;/span&gt;&lt;span class="nb"&gt;type&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="fm"&gt;__getattribute__&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Foo&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;bar&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;  &lt;span class="c1"&gt;# called!&lt;/span&gt;
&lt;span class="linenos"&gt;10&lt;/span&gt;&lt;span class="nb"&gt;object&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="fm"&gt;__getattribute__&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Foo&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;bar&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;  &lt;span class="c1"&gt;# ...&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;And that&amp;#8217;s not all!  The&amp;nbsp;mysterious &lt;code&gt;super&lt;/code&gt; function, used to exhaustively traverse superclass method calls even in the face of diamond inheritance, can &lt;em&gt;also&lt;/em&gt; be expressed in pure Python using these primitives.  You could write your own superclass calling convention and use it exactly the same way&amp;nbsp;as &lt;code&gt;super&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;This is one of the things I really like about Python.  Very little of it is truly magical; virtually everything about the object model exists in the &lt;em&gt;types&lt;/em&gt; rather than the &lt;em&gt;language&lt;/em&gt;, which means virtually everything can be &lt;em&gt;customized&lt;/em&gt; in pure&amp;nbsp;Python.&lt;/p&gt;
&lt;h3 id="class-creation-and-metaclasses"&gt;&lt;a class="toclink" href="#class-creation-and-metaclasses"&gt;Class creation and metaclasses&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;A very brief word on all of this stuff, since I could talk forever about Python and I have three other languages to get&amp;nbsp;to.&lt;/p&gt;
&lt;p&gt;The &lt;code&gt;class&lt;/code&gt; block itself is fairly interesting.  It looks like&amp;nbsp;this:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="linenos"&gt;1&lt;/span&gt;&lt;span class="k"&gt;class&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nc"&gt;Name&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;bases&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;**&lt;/span&gt;&lt;span class="n"&gt;kwargs&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
&lt;span class="linenos"&gt;2&lt;/span&gt;    &lt;span class="c1"&gt;# code&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;I&amp;#8217;ve said several times that classes are objects, and in fact&amp;nbsp;the &lt;code&gt;class&lt;/code&gt; block is one big pile of syntactic sugar for&amp;nbsp;calling &lt;code&gt;type(...)&lt;/code&gt; with some arguments to create a&amp;nbsp;new &lt;code&gt;type&lt;/code&gt; object.&lt;/p&gt;
&lt;p&gt;The Python documentation has a &lt;a href="https://docs.python.org/3/reference/datamodel.html#customizing-class-creation"&gt;remarkably detailed description of this process&lt;/a&gt;, but the gist&amp;nbsp;is:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Python determines the type of the new class — the &lt;em&gt;metaclass&lt;/em&gt; — by looking for&amp;nbsp;a &lt;code&gt;metaclass&lt;/code&gt; keyword argument.  If there isn&amp;#8217;t one, Python uses the &amp;#8220;lowest&amp;#8221; type among the provided base classes.  (If you&amp;#8217;re not doing anything special, that&amp;#8217;ll just&amp;nbsp;be &lt;code&gt;type&lt;/code&gt;, since every class inherits&amp;nbsp;from &lt;code&gt;object&lt;/code&gt; and &lt;code&gt;object&lt;/code&gt; is an instance&amp;nbsp;of &lt;code&gt;type&lt;/code&gt;.)&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Python executes the class body.  It gets its own local scope, and any assignments or method definitions go into that&amp;nbsp;scope.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Python now&amp;nbsp;calls &lt;code&gt;type(name, bases, attrs, **kwargs)&lt;/code&gt;.  The name is whatever was right&amp;nbsp;after &lt;code&gt;class&lt;/code&gt;;&amp;nbsp;the &lt;code&gt;bases&lt;/code&gt; are position arguments;&amp;nbsp;and &lt;code&gt;attrs&lt;/code&gt; is the class body&amp;#8217;s local scope.  (This is how methods and other class attributes end up on the class.)  The brand new type is then assigned&amp;nbsp;to &lt;code&gt;Name&lt;/code&gt;.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Of course, you can mess with most of this.  You can&amp;nbsp;implement &lt;code&gt;__prepare__&lt;/code&gt; on a metaclass, for example, to use a custom mapping as storage for the local scope — including any &lt;em&gt;reads&lt;/em&gt;, which allows for some interesting shenanigans.  The only part you &lt;em&gt;can&amp;#8217;t&lt;/em&gt; really implement in pure Python is the scoping bit, which has a couple extra rules that make sense for classes.  (In particular, functions defined within&amp;nbsp;a &lt;code&gt;class&lt;/code&gt; block don&amp;#8217;t close over the class body; that would be&amp;nbsp;nonsense.)&lt;/p&gt;
&lt;h3 id="object-creation"&gt;&lt;a class="toclink" href="#object-creation"&gt;Object creation&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Finally, there&amp;#8217;s what actually happens when you create an object — including a class, which remember is just an invocation&amp;nbsp;of &lt;code&gt;type(...)&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Calling &lt;code&gt;Foo(...)&lt;/code&gt; is implemented as, well, &lt;em&gt;a call&lt;/em&gt;.  Any type can implement calls with&amp;nbsp;the &lt;code&gt;__call__&lt;/code&gt; special method, and you&amp;#8217;ll find&amp;nbsp;that &lt;code&gt;type&lt;/code&gt; itself does so.  It looks something like&amp;nbsp;this:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="linenos"&gt; 1&lt;/span&gt;&lt;span class="c1"&gt;# oh, a fun wrinkle that&amp;#39;s hard to express in pure python: type is a class, so&lt;/span&gt;
&lt;span class="linenos"&gt; 2&lt;/span&gt;&lt;span class="c1"&gt;# it&amp;#39;s an instance of itself&lt;/span&gt;
&lt;span class="linenos"&gt; 3&lt;/span&gt;&lt;span class="k"&gt;class&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nc"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="linenos"&gt; 4&lt;/span&gt;    &lt;span class="k"&gt;def&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="fm"&gt;__call__&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="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;**&lt;/span&gt;&lt;span class="n"&gt;kwargs&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
&lt;span class="linenos"&gt; 5&lt;/span&gt;        &lt;span class="c1"&gt;# remember, here &amp;#39;self&amp;#39; is a CLASS, an instance of type.&lt;/span&gt;
&lt;span class="linenos"&gt; 6&lt;/span&gt;        &lt;span class="c1"&gt;# __new__ is a true constructor: object.__new__ allocates storage&lt;/span&gt;
&lt;span class="linenos"&gt; 7&lt;/span&gt;        &lt;span class="c1"&gt;# for a new blank object&lt;/span&gt;
&lt;span class="linenos"&gt; 8&lt;/span&gt;        &lt;span class="n"&gt;instance&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="fm"&gt;__new__&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="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;**&lt;/span&gt;&lt;span class="n"&gt;kwargs&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="linenos"&gt; 9&lt;/span&gt;        &lt;span class="c1"&gt;# you can return whatever you want from __new__ (!), and __init__&lt;/span&gt;
&lt;span class="linenos"&gt;10&lt;/span&gt;        &lt;span class="c1"&gt;# is only called on it if it&amp;#39;s of the right type&lt;/span&gt;
&lt;span class="linenos"&gt;11&lt;/span&gt;        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="nb"&gt;isinstance&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;instance&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="linenos"&gt;12&lt;/span&gt;            &lt;span class="n"&gt;instance&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="fm"&gt;__init__&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;**&lt;/span&gt;&lt;span class="n"&gt;kwargs&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="linenos"&gt;13&lt;/span&gt;        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;instance&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Again, you can trivially confirm this by asking any type for&amp;nbsp;its &lt;code&gt;__call__&lt;/code&gt; method.  Assuming that type doesn&amp;#8217;t&amp;nbsp;implement &lt;code&gt;__call__&lt;/code&gt; itself, you&amp;#8217;ll get back a bound version&amp;nbsp;of &lt;code&gt;type&lt;/code&gt;&lt;span class="quo"&gt;&amp;#8216;&lt;/span&gt;s&amp;nbsp;implementation.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="linenos"&gt;1&lt;/span&gt;&lt;span class="gp"&gt;&amp;gt;&amp;gt;&amp;gt; &lt;/span&gt;&lt;span class="nb"&gt;list&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="fm"&gt;__call__&lt;/span&gt;
&lt;span class="linenos"&gt;2&lt;/span&gt;&lt;span class="go"&gt;&amp;lt;method-wrapper &amp;#39;__call__&amp;#39; of type object at 0x7fafb831a400&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;You can thus&amp;nbsp;implement &lt;code&gt;__call__&lt;/code&gt; in your own metaclass to completely change how subclasses are created — including skipping the creation altogether, if you&amp;nbsp;like.&lt;/p&gt;
&lt;p&gt;And&amp;#8230;  there&amp;#8217;s a bunch of stuff I haven&amp;#8217;t even touched&amp;nbsp;on.&lt;/p&gt;
&lt;h3 id="the-python-philosophy"&gt;&lt;a class="toclink" href="#the-python-philosophy"&gt;The Python philosophy&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Python offers something that, on the surface, looks like a &amp;#8220;traditional&amp;#8221; class/object model.  Under the hood, it acts more like a prototypical system, where failed attribute lookups simply defer to a superclass or&amp;nbsp;metaclass.&lt;/p&gt;
&lt;p&gt;The language also goes to almost &lt;em&gt;superhuman&lt;/em&gt; lengths to expose all of its moving parts.  Even the prototypical behavior is an implementation&amp;nbsp;of &lt;code&gt;__getattribute__&lt;/code&gt; somewhere, which you are free to completely replace in your own types.  Proxying and delegation are&amp;nbsp;easy.&lt;/p&gt;
&lt;p&gt;Also very nice is that these features &amp;#8220;bundle&amp;#8221; well, by which I mean a library author can do all manner of convoluted hijinks, and a consumer of that library doesn&amp;#8217;t have to see any of it or understand how it works.  You only need to inherit from a particular class (which has a metaclass), or use some descriptor as a decorator, or even learn any new&amp;nbsp;syntax.&lt;/p&gt;
&lt;p&gt;This meshes well with Python culture, which is pretty big on the principle of least surprise.  These super-advanced features tend to be tightly confined to single simple features (like &amp;#8220;makes a &lt;a href="http://classtools.readthedocs.io/en/latest/#classtools.weakattr"&gt;weak attribute&lt;/a&gt;&amp;#8220;) or cordoned with DSLs (e.g., defining a form/struct/database table with a class body).  In particular, I&amp;#8217;ve never seen a metaclass in the wild implement its&amp;nbsp;own &lt;code&gt;__call__&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;I have mixed feelings about that.  It&amp;#8217;s probably a good thing overall that the Python world shows such restraint, but I wonder if there are some very interesting possibilities we&amp;#8217;re missing out on.  I implemented a&amp;nbsp;metaclass &lt;code&gt;__call__&lt;/code&gt; myself, just once, in an entity/component system that strove to minimize fuss when communicating between components.  It never saw the light of day, but I enjoyed seeing some new things Python could do with the same relatively simple syntax.  I wouldn&amp;#8217;t mind seeing, say, an object model based on composition (with no inheritance) built atop Python&amp;#8217;s&amp;nbsp;primitives.&lt;/p&gt;
&lt;h2 id="lua"&gt;&lt;a class="toclink" href="#lua"&gt;Lua&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Lua doesn&amp;#8217;t have an object model.  Instead, it gives you a handful of very small primitives for building your own object model.  This is pretty typical of Lua — it&amp;#8217;s a very powerful language, but has been carefully constructed to be very small at the same time.  I&amp;#8217;ve never encountered anything else quite like it, and &amp;#8220;but it starts indexing at 1!&amp;#8221; really doesn&amp;#8217;t do it&amp;nbsp;justice.&lt;/p&gt;
&lt;p&gt;The best way to demonstrate how objects work in Lua is to build some from scratch.  We need two key features.  The first is &lt;strong&gt;metatables&lt;/strong&gt;, which bear a passing resemblance to Python&amp;#8217;s&amp;nbsp;metaclasses.&lt;/p&gt;
&lt;h3 id="tables-and-metatables"&gt;&lt;a class="toclink" href="#tables-and-metatables"&gt;Tables and metatables&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;The &lt;em&gt;table&lt;/em&gt; is Lua&amp;#8217;s mapping type and its primary data structure.  Keys can be any value other&amp;nbsp;than &lt;code&gt;nil&lt;/code&gt;.  Lists are implemented as tables whose keys are consecutive integers starting from 1.  Nothing terribly surprising.  The dot operator is sugar for indexing with a string&amp;nbsp;key.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="linenos"&gt;1&lt;/span&gt;&lt;span class="kd"&gt;local&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;t&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="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&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="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="nv"&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="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="linenos"&gt;2&lt;/span&gt;&lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;t&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;a&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="c1"&gt;-- 1&lt;/span&gt;
&lt;span class="linenos"&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="nv"&gt;t&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="py"&gt;b&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="c1"&gt;-- 2&lt;/span&gt;
&lt;span class="linenos"&gt;4&lt;/span&gt;&lt;span class="nv"&gt;t&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="py"&gt;c&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;3&lt;/span&gt;
&lt;span class="linenos"&gt;5&lt;/span&gt;&lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;t&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;c&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="c1"&gt;-- 3&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;A metatable is a table that can be associated with another value (usually another table) to change its behavior.  For example, operator overloading is implemented by assigning a function to a special key in a&amp;nbsp;metatable.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="linenos"&gt; 1&lt;/span&gt;&lt;span class="kd"&gt;local&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;t&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="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&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="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="nv"&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="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="linenos"&gt; 2&lt;/span&gt;&lt;span class="c1"&gt;--print(t + 0)  -- error: attempt to perform arithmetic on a table value&lt;/span&gt;
&lt;span class="linenos"&gt; 3&lt;/span&gt;
&lt;span class="linenos"&gt; 4&lt;/span&gt;&lt;span class="kd"&gt;local&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;mt&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="p"&gt;{&lt;/span&gt;
&lt;span class="linenos"&gt; 5&lt;/span&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nv"&gt;__add&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="kr"&gt;function&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;left&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;right&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="linenos"&gt; 6&lt;/span&gt;&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="kr"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;12&lt;/span&gt;
&lt;span class="linenos"&gt; 7&lt;/span&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="kr"&gt;end&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="linenos"&gt; 8&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="linenos"&gt; 9&lt;/span&gt;&lt;span class="nb"&gt;setmetatable&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;t&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;mt&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="linenos"&gt;10&lt;/span&gt;&lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;t&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;-- 12&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Now, the interesting part: one of the special keys&amp;nbsp;is &lt;code&gt;__index&lt;/code&gt;, which is consulted when the base table is &lt;em&gt;indexed&lt;/em&gt; by a key it doesn&amp;#8217;t contain.  Here&amp;#8217;s a table that claims every key maps to&amp;nbsp;itself.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="linenos"&gt; 1&lt;/span&gt;&lt;span class="kd"&gt;local&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;t&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="p"&gt;{}&lt;/span&gt;
&lt;span class="linenos"&gt; 2&lt;/span&gt;&lt;span class="kd"&gt;local&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;mt&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="p"&gt;{&lt;/span&gt;
&lt;span class="linenos"&gt; 3&lt;/span&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nv"&gt;__index&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="kr"&gt;function&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;table&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;key&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="linenos"&gt; 4&lt;/span&gt;&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="kr"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;key&lt;/span&gt;
&lt;span class="linenos"&gt; 5&lt;/span&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="kr"&gt;end&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="linenos"&gt; 6&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="linenos"&gt; 7&lt;/span&gt;&lt;span class="nb"&gt;setmetatable&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;t&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;mt&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="linenos"&gt; 8&lt;/span&gt;&lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;t&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="py"&gt;foo&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="c1"&gt;-- foo&lt;/span&gt;
&lt;span class="linenos"&gt; 9&lt;/span&gt;&lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;t&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="py"&gt;bar&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="c1"&gt;-- bar&lt;/span&gt;
&lt;span class="linenos"&gt;10&lt;/span&gt;&lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;t&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="w"&gt;  &lt;/span&gt;&lt;span class="c1"&gt;-- 3&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;&lt;code&gt;__index&lt;/code&gt; doesn&amp;#8217;t have to be a function, either.  It can be yet another table, in which case that table is simply indexed with the key.  If the key still doesn&amp;#8217;t exist and &lt;em&gt;that&lt;/em&gt; table has a metatable with&amp;nbsp;an &lt;code&gt;__index&lt;/code&gt;, the process&amp;nbsp;repeats.&lt;/p&gt;
&lt;p&gt;With this, it&amp;#8217;s easy to have several unrelated tables that act as a single table.  Call the base table an &lt;em&gt;object&lt;/em&gt;, fill&amp;nbsp;the &lt;code&gt;__index&lt;/code&gt; table with functions and call it a &lt;em&gt;class&lt;/em&gt;, and you have half of an object system.  You can even get &lt;em&gt;prototypical inheritance&lt;/em&gt; by&amp;nbsp;chaining &lt;code&gt;__index&lt;/code&gt;es&amp;nbsp;together.&lt;/p&gt;
&lt;p&gt;At this point things are a little confusing, since we have at least three tables going on, so here&amp;#8217;s a diagram.  Keep in mind that Lua doesn&amp;#8217;t actually have anything called an &amp;#8220;object&amp;#8221;, &amp;#8220;class&amp;#8221;, or &amp;#8220;method&amp;#8221; — those are just convenient nicknames for a particular structure we might build with Lua&amp;#8217;s&amp;nbsp;primitives.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="linenos"&gt; 1&lt;/span&gt;                    ╔═══════════╗        ...
&lt;span class="linenos"&gt; 2&lt;/span&gt;                    ║ metatable ║         ║
&lt;span class="linenos"&gt; 3&lt;/span&gt;                    ╟───────────╢   ┌─────╨───────────────────────┐
&lt;span class="linenos"&gt; 4&lt;/span&gt;                    ║ __index   ╫───┤ lookup table (&amp;quot;superclass&amp;quot;) │
&lt;span class="linenos"&gt; 5&lt;/span&gt;                    ╚═══╦═══════╝   ├─────────────────────────────┤
&lt;span class="linenos"&gt; 6&lt;/span&gt;  ╔═══════════╗         ║           │ some other method           ┼─── function() ... end
&lt;span class="linenos"&gt; 7&lt;/span&gt;  ║ metatable ║         ║           └─────────────────────────────┘
&lt;span class="linenos"&gt; 8&lt;/span&gt;  ╟───────────╢   ┌─────╨──────────────────┐
&lt;span class="linenos"&gt; 9&lt;/span&gt;  ║ __index   ╫───┤ lookup table (&amp;quot;class&amp;quot;) │
&lt;span class="linenos"&gt;10&lt;/span&gt;  ╚═══╦═══════╝   ├────────────────────────┤
&lt;span class="linenos"&gt;11&lt;/span&gt;      ║           │ some method            ┼─── function() ... end
&lt;span class="linenos"&gt;12&lt;/span&gt;      ║           └────────────────────────┘
&lt;span class="linenos"&gt;13&lt;/span&gt;┌─────╨─────────────────┐
&lt;span class="linenos"&gt;14&lt;/span&gt;│ base table (&amp;quot;object&amp;quot;) │
&lt;span class="linenos"&gt;15&lt;/span&gt;└───────────────────────┘
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Note that a metatable is &lt;em&gt;not&lt;/em&gt; the same as a class; it defines behavior, not methods.  Conversely, if you try to use a class directly as a metatable, it will probably not do much.  (This is pretty different from e.g. Python, where operator overloads are just methods with funny names.  One nice thing about the Lua approach is that you can keep interface-like functionality separate from methods, and avoid clogging up arbitrary objects&amp;#8217; namespaces.  You could even use a dummy table as a key and completely avoid name&amp;nbsp;collisions.)&lt;/p&gt;
&lt;p&gt;Anyway,&amp;nbsp;code!&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="linenos"&gt; 1&lt;/span&gt;&lt;span class="kd"&gt;local&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;class&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="p"&gt;{&lt;/span&gt;
&lt;span class="linenos"&gt; 2&lt;/span&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nv"&gt;foo&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="kr"&gt;function&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;a&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="linenos"&gt; 3&lt;/span&gt;&lt;span class="w"&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;foo got&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;a&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="linenos"&gt; 4&lt;/span&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="kr"&gt;end&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="linenos"&gt; 5&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="linenos"&gt; 6&lt;/span&gt;&lt;span class="kd"&gt;local&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;mt&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="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;__index&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="nv"&gt;class&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="linenos"&gt; 7&lt;/span&gt;&lt;span class="c1"&gt;-- setmetatable returns its first argument, so this is nice shorthand&lt;/span&gt;
&lt;span class="linenos"&gt; 8&lt;/span&gt;&lt;span class="kd"&gt;local&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;obj1&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;setmetatable&lt;/span&gt;&lt;span class="p"&gt;({},&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;mt&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="linenos"&gt; 9&lt;/span&gt;&lt;span class="kd"&gt;local&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;obj2&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;setmetatable&lt;/span&gt;&lt;span class="p"&gt;({},&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;mt&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="linenos"&gt;10&lt;/span&gt;&lt;span class="nv"&gt;obj1&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;foo&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;7&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="c1"&gt;-- foo got 7&lt;/span&gt;
&lt;span class="linenos"&gt;11&lt;/span&gt;&lt;span class="nv"&gt;obj2&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;foo&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;9&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="c1"&gt;-- foo got 9&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Wait, wait, hang on.  Didn&amp;#8217;t I call these &lt;em&gt;methods&lt;/em&gt;?  How do they get at the object?  Maybe Lua has a&amp;nbsp;magical &lt;code&gt;this&lt;/code&gt; variable?&lt;/p&gt;
&lt;h3 id="methods-sort-of"&gt;&lt;a class="toclink" href="#methods-sort-of"&gt;Methods, sort of&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Not quite, but this is where the other key feature comes in: &lt;strong&gt;method-call syntax&lt;/strong&gt;.  It&amp;#8217;s the lightest touch of sugar, just enough to have method&amp;nbsp;invocation.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="linenos"&gt;1&lt;/span&gt;&lt;span class="c1"&gt;-- note the colon!&lt;/span&gt;
&lt;span class="linenos"&gt;2&lt;/span&gt;&lt;span class="nv"&gt;a&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="nf"&gt;b&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;c&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;d&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="linenos"&gt;3&lt;/span&gt;
&lt;span class="linenos"&gt;4&lt;/span&gt;&lt;span class="c1"&gt;-- exactly equivalent to this&lt;/span&gt;
&lt;span class="linenos"&gt;5&lt;/span&gt;&lt;span class="c1"&gt;-- (except that `a` is only evaluated once)&lt;/span&gt;
&lt;span class="linenos"&gt;6&lt;/span&gt;&lt;span class="nv"&gt;a&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;b&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;c&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;d&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="linenos"&gt;7&lt;/span&gt;
&lt;span class="linenos"&gt;8&lt;/span&gt;&lt;span class="c1"&gt;-- which of course is really this&lt;/span&gt;
&lt;span class="linenos"&gt;9&lt;/span&gt;&lt;span class="nv"&gt;a&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;b&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;](&lt;/span&gt;&lt;span class="nv"&gt;a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;c&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;d&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;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Now we can write methods that actually do&amp;nbsp;something.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="linenos"&gt; 1&lt;/span&gt;&lt;span class="kd"&gt;local&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;class&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="p"&gt;{&lt;/span&gt;
&lt;span class="linenos"&gt; 2&lt;/span&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nv"&gt;bar&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="kr"&gt;function&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;self&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="linenos"&gt; 3&lt;/span&gt;&lt;span class="w"&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;our score is&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="py"&gt;score&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="linenos"&gt; 4&lt;/span&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="kr"&gt;end&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="linenos"&gt; 5&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="linenos"&gt; 6&lt;/span&gt;&lt;span class="kd"&gt;local&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;mt&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="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;__index&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="nv"&gt;class&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="linenos"&gt; 7&lt;/span&gt;&lt;span class="kd"&gt;local&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;obj1&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;setmetatable&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;score&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;13&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="nv"&gt;mt&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="linenos"&gt; 8&lt;/span&gt;&lt;span class="kd"&gt;local&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;obj2&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;setmetatable&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;score&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;25&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="nv"&gt;mt&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="linenos"&gt; 9&lt;/span&gt;&lt;span class="nv"&gt;obj1&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="nf"&gt;bar&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="c1"&gt;-- our score is 13&lt;/span&gt;
&lt;span class="linenos"&gt;10&lt;/span&gt;&lt;span class="nv"&gt;obj2&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="nf"&gt;bar&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="c1"&gt;-- our score is 25&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;And that&amp;#8217;s all you need.  Much like Python, methods and data live in the same namespace, and Lua doesn&amp;#8217;t care&amp;nbsp;whether &lt;code&gt;obj:method()&lt;/code&gt; finds a function&amp;nbsp;on &lt;code&gt;obj&lt;/code&gt; or gets one from the&amp;nbsp;metatable&amp;#8217;s &lt;code&gt;__index&lt;/code&gt;.  &lt;em&gt;Unlike&lt;/em&gt; Python, the function will be&amp;nbsp;passed &lt;code&gt;self&lt;/code&gt; either way,&amp;nbsp;because &lt;code&gt;self&lt;/code&gt; comes from the use&amp;nbsp;of &lt;code&gt;:&lt;/code&gt; rather than from the lookup&amp;nbsp;behavior.&lt;/p&gt;
&lt;p&gt;(Aside: strictly speaking, &lt;em&gt;any&lt;/em&gt; Lua value can have a metatable — and if you try to index a non-table, Lua will &lt;em&gt;always&lt;/em&gt; consult the&amp;nbsp;metatable&amp;#8217;s &lt;code&gt;__index&lt;/code&gt;.  Strings all have&amp;nbsp;the &lt;code&gt;string&lt;/code&gt; library as a metatable, so you can call methods on them:&amp;nbsp;try &lt;code&gt;("%s %s"):format(1, 2)&lt;/code&gt;.  Numbers, strings, functions, and nil each share a type-specific metatable, and you can only change it with&amp;nbsp;the &lt;code&gt;debug&lt;/code&gt; library which is often unavailable, so this is of limited use.  But if you&amp;#8217;re writing Lua bindings from C, you can give your pointers metatables directly to give them methods implemented in&amp;nbsp;C.)&lt;/p&gt;
&lt;h3 id="bringing-it-all-together"&gt;&lt;a class="toclink" href="#bringing-it-all-together"&gt;Bringing it all together&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Of course, writing all this stuff every time is a little tedious and error-prone, so instead you might want to wrap it all up inside a little function.  No&amp;nbsp;problem.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="linenos"&gt; 1&lt;/span&gt;&lt;span class="kd"&gt;local&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kr"&gt;function&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;make_object&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;body&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="linenos"&gt; 2&lt;/span&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="c1"&gt;-- create a metatable&lt;/span&gt;
&lt;span class="linenos"&gt; 3&lt;/span&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="kd"&gt;local&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;mt&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="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;__index&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="nv"&gt;body&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="linenos"&gt; 4&lt;/span&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="c1"&gt;-- create a base table to serve as the object itself&lt;/span&gt;
&lt;span class="linenos"&gt; 5&lt;/span&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="kd"&gt;local&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;obj&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;setmetatable&lt;/span&gt;&lt;span class="p"&gt;({},&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;mt&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="linenos"&gt; 6&lt;/span&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="c1"&gt;-- and, done&lt;/span&gt;
&lt;span class="linenos"&gt; 7&lt;/span&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="kr"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;obj&lt;/span&gt;
&lt;span class="linenos"&gt; 8&lt;/span&gt;&lt;span class="kr"&gt;end&lt;/span&gt;
&lt;span class="linenos"&gt; 9&lt;/span&gt;
&lt;span class="linenos"&gt;10&lt;/span&gt;&lt;span class="kd"&gt;local&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;Dog&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="p"&gt;{&lt;/span&gt;
&lt;span class="linenos"&gt;11&lt;/span&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="c1"&gt;-- this acts as a &amp;quot;default&amp;quot; value; if obj.barks is missing, __index will&lt;/span&gt;
&lt;span class="linenos"&gt;12&lt;/span&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="c1"&gt;-- kick in and find this value on the class.  but if obj.barks is assigned&lt;/span&gt;
&lt;span class="linenos"&gt;13&lt;/span&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="c1"&gt;-- to, it&amp;#39;ll go in the object and shadow the value here.&lt;/span&gt;
&lt;span class="linenos"&gt;14&lt;/span&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nv"&gt;barks&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="linenos"&gt;15&lt;/span&gt;
&lt;span class="linenos"&gt;16&lt;/span&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nv"&gt;bark&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="kr"&gt;function&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;self&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="linenos"&gt;17&lt;/span&gt;&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nv"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="py"&gt;barks&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="nv"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="py"&gt;barks&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="linenos"&gt;18&lt;/span&gt;&lt;span class="w"&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;woof!&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="linenos"&gt;19&lt;/span&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="kr"&gt;end&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="linenos"&gt;20&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="linenos"&gt;21&lt;/span&gt;
&lt;span class="linenos"&gt;22&lt;/span&gt;&lt;span class="kd"&gt;local&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;mydog&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;make_object&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;Dog&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="linenos"&gt;23&lt;/span&gt;&lt;span class="nv"&gt;mydog&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="nf"&gt;bark&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="c1"&gt;-- woof!&lt;/span&gt;
&lt;span class="linenos"&gt;24&lt;/span&gt;&lt;span class="nv"&gt;mydog&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="nf"&gt;bark&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="c1"&gt;-- woof!&lt;/span&gt;
&lt;span class="linenos"&gt;25&lt;/span&gt;&lt;span class="nv"&gt;mydog&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="nf"&gt;bark&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="c1"&gt;-- woof!&lt;/span&gt;
&lt;span class="linenos"&gt;26&lt;/span&gt;&lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;mydog&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="py"&gt;barks&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="c1"&gt;-- 3&lt;/span&gt;
&lt;span class="linenos"&gt;27&lt;/span&gt;&lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;Dog&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="py"&gt;barks&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="c1"&gt;-- 0&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;It works, but it&amp;#8217;s fairly barebones.  The nice thing is that you can extend it pretty much however you want.  I won&amp;#8217;t reproduce an entire serious object system here — lord knows there are enough of them floating around — but the implementation I have for my LÖVE games lets me do&amp;nbsp;this:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="linenos"&gt; 1&lt;/span&gt;&lt;span class="kd"&gt;local&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;Animal&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="nv"&gt;Object&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="py"&gt;extend&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="linenos"&gt; 2&lt;/span&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nv"&gt;cries&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="linenos"&gt; 3&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="linenos"&gt; 4&lt;/span&gt;
&lt;span class="linenos"&gt; 5&lt;/span&gt;&lt;span class="c1"&gt;-- called automatically by Object&lt;/span&gt;
&lt;span class="linenos"&gt; 6&lt;/span&gt;&lt;span class="kr"&gt;function&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nc"&gt;Animal&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="linenos"&gt; 7&lt;/span&gt;&lt;span class="w"&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;whoops i couldn&amp;#39;t think of anything interesting to put here&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="linenos"&gt; 8&lt;/span&gt;&lt;span class="kr"&gt;end&lt;/span&gt;
&lt;span class="linenos"&gt; 9&lt;/span&gt;
&lt;span class="linenos"&gt;10&lt;/span&gt;&lt;span class="c1"&gt;-- this is just nice syntax for adding a first argument called &amp;#39;self&amp;#39;, then&lt;/span&gt;
&lt;span class="linenos"&gt;11&lt;/span&gt;&lt;span class="c1"&gt;-- assigning this function to Animal.cry&lt;/span&gt;
&lt;span class="linenos"&gt;12&lt;/span&gt;&lt;span class="kr"&gt;function&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nc"&gt;Animal&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="nf"&gt;cry&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="linenos"&gt;13&lt;/span&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nv"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="py"&gt;cries&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="nv"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="py"&gt;cries&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="linenos"&gt;14&lt;/span&gt;&lt;span class="kr"&gt;end&lt;/span&gt;
&lt;span class="linenos"&gt;15&lt;/span&gt;
&lt;span class="linenos"&gt;16&lt;/span&gt;&lt;span class="kd"&gt;local&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;Cat&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="nv"&gt;Animal&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="py"&gt;extend&lt;/span&gt;&lt;span class="p"&gt;{}&lt;/span&gt;
&lt;span class="linenos"&gt;17&lt;/span&gt;
&lt;span class="linenos"&gt;18&lt;/span&gt;&lt;span class="kr"&gt;function&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nc"&gt;Cat&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="nf"&gt;cry&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="linenos"&gt;19&lt;/span&gt;&lt;span class="w"&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;meow!&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="linenos"&gt;20&lt;/span&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nv"&gt;Cat&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="py"&gt;__super&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;cry&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;self&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="linenos"&gt;21&lt;/span&gt;&lt;span class="kr"&gt;end&lt;/span&gt;
&lt;span class="linenos"&gt;22&lt;/span&gt;
&lt;span class="linenos"&gt;23&lt;/span&gt;&lt;span class="kd"&gt;local&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;cat&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;Cat&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="linenos"&gt;24&lt;/span&gt;&lt;span class="nv"&gt;cat&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="nf"&gt;cry&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="c1"&gt;-- meow!&lt;/span&gt;
&lt;span class="linenos"&gt;25&lt;/span&gt;&lt;span class="nv"&gt;cat&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="nf"&gt;cry&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="c1"&gt;-- meow!&lt;/span&gt;
&lt;span class="linenos"&gt;26&lt;/span&gt;&lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;cat&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="py"&gt;cries&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="c1"&gt;-- 2&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;When I say you can extend it however you want, I mean that.  I could&amp;#8217;ve implemented Python&amp;nbsp;(2)-style &lt;code&gt;super(Cat, self):cry()&lt;/code&gt; syntax; I just never got around to it.  I could even make it work with multiple inheritance if I really wanted to — or I could go the complete opposite direction and only implement composition.  I could implement descriptors, customizing the behavior of individual table keys.  I could add pretty decent syntax for composition/proxying.  I am trying very hard to end this section&amp;nbsp;now.&lt;/p&gt;
&lt;h3 id="the-lua-philosophy"&gt;&lt;a class="toclink" href="#the-lua-philosophy"&gt;The Lua philosophy&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Lua&amp;#8217;s philosophy is to&amp;#8230;  not have a philosophy?  It gives you the bare minimum to make objects work, and you can do absolutely whatever you want from there.  Lua does have something resembling prototypical inheritance, but it&amp;#8217;s not so much a first-class feature as an emergent property of some very simple tools.  And since you can&amp;nbsp;make &lt;code&gt;__index&lt;/code&gt; be a function, you could avoid the prototypical behavior and do something different&amp;nbsp;entirely.&lt;/p&gt;
&lt;p&gt;The very severe downside, of course, is that you have to find or build your own object system — which can get pretty confusing very quickly, what with the multiple small moving parts.  Third-party code may also have its &lt;em&gt;own&lt;/em&gt; object system with subtly different behavior.  (Though, in my experience, third-party code tries very hard to avoid needing an object system at&amp;nbsp;all.)&lt;/p&gt;
&lt;p&gt;It&amp;#8217;s hard to say what the Lua &amp;#8220;culture&amp;#8221; is like, since Lua is an embedded language that&amp;#8217;s often a little different in each environment.  I imagine it has a thousand millicultures, instead.  I &lt;em&gt;can&lt;/em&gt; say that the tedium of building my own object model has led &lt;em&gt;me&lt;/em&gt; into something very &amp;#8220;traditional&amp;#8221;, with prototypical inheritance and whatnot.  It&amp;#8217;s partly what I&amp;#8217;m used to, but it&amp;#8217;s also just really dang easy to get&amp;nbsp;working.&lt;/p&gt;
&lt;p&gt;Likewise, while I love properties in Python and use them all the dang time, I&amp;#8217;ve yet to use a single one in Lua.  They wouldn&amp;#8217;t be particularly hard to add to my object model, but having to add them myself (or shop around for an object model with them &lt;em&gt;and also&lt;/em&gt; port all my code to use it) adds a huge amount of friction.  I&amp;#8217;ve thought about designing an interesting &lt;span class="caps"&gt;ECS&lt;/span&gt; with custom object behavior, too, but…  is it really worth the effort?  For all the power and flexibility Lua offers, the cost is that by the time I have something working at all, I&amp;#8217;m too exhausted to actually &lt;em&gt;use&lt;/em&gt; any of&amp;nbsp;it.&lt;/p&gt;
&lt;h2 id="javascript"&gt;&lt;a class="toclink" href="#javascript"&gt;JavaScript&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;JavaScript is notable for being preposterously heavily used, yet not having&amp;nbsp;a &lt;code&gt;class&lt;/code&gt; block.&lt;/p&gt;
&lt;p&gt;Well.  Okay.  Yes.  &lt;a href="https://eev.ee/blog/2017/10/07/javascript-got-better-while-i-wasnt-looking/"&gt;It has one &lt;em&gt;now&lt;/em&gt;.&lt;/a&gt;  It didn&amp;#8217;t for a very long time, and even the one it has now is&amp;nbsp;sugar.&lt;/p&gt;
&lt;p&gt;Here&amp;#8217;s a vector class&amp;nbsp;again:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="linenos"&gt; 1&lt;/span&gt;&lt;span class="kd"&gt;class&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;Vector&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="linenos"&gt; 2&lt;/span&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="kr"&gt;constructor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;x&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;y&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="linenos"&gt; 3&lt;/span&gt;&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;x&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="nx"&gt;x&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="linenos"&gt; 4&lt;/span&gt;&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;y&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="nx"&gt;y&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="linenos"&gt; 5&lt;/span&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="linenos"&gt; 6&lt;/span&gt;
&lt;span class="linenos"&gt; 7&lt;/span&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;get&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;magnitude&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="linenos"&gt; 8&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;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;sqrt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;x&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;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;x&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;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;y&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;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;y&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="linenos"&gt; 9&lt;/span&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="linenos"&gt;10&lt;/span&gt;
&lt;span class="linenos"&gt;11&lt;/span&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;dot&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;other&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="linenos"&gt;12&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="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;x&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="nx"&gt;other&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;x&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;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;y&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="nx"&gt;other&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;y&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="linenos"&gt;13&lt;/span&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="linenos"&gt;14&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;In &amp;#8220;classic&amp;#8221; JavaScript, this would be written&amp;nbsp;as:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="linenos"&gt; 1&lt;/span&gt;&lt;span class="kd"&gt;function&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;Vector&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;x&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;y&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="linenos"&gt; 2&lt;/span&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;x&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="nx"&gt;x&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="linenos"&gt; 3&lt;/span&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;y&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="nx"&gt;y&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="linenos"&gt; 4&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="linenos"&gt; 5&lt;/span&gt;
&lt;span class="linenos"&gt; 6&lt;/span&gt;&lt;span class="nb"&gt;Object&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;defineProperty&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;Vector&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;prototype&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;magnitude&amp;#39;&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="linenos"&gt; 7&lt;/span&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;configurable&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="linenos"&gt; 8&lt;/span&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;enumerable&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="linenos"&gt; 9&lt;/span&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;get&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kd"&gt;function&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="linenos"&gt;10&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;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;sqrt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;x&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;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;x&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;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;y&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;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;y&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="linenos"&gt;11&lt;/span&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="linenos"&gt;12&lt;/span&gt;&lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="linenos"&gt;13&lt;/span&gt;
&lt;span class="linenos"&gt;14&lt;/span&gt;
&lt;span class="linenos"&gt;15&lt;/span&gt;&lt;span class="nx"&gt;Vector&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;prototype&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;dot&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="kd"&gt;function&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;other&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="linenos"&gt;16&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="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;x&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="nx"&gt;other&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;x&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;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;y&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="nx"&gt;other&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;y&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="linenos"&gt;17&lt;/span&gt;&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Hm, yes.  I can see why they&amp;nbsp;added &lt;code&gt;class&lt;/code&gt;.&lt;/p&gt;
&lt;h3 id="the-javascript-model"&gt;&lt;a class="toclink" href="#the-javascript-model"&gt;The JavaScript model&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;In JavaScript, a new type is defined in terms of a &lt;em&gt;function&lt;/em&gt;, which is its&amp;nbsp;constructor.&lt;/p&gt;
&lt;p&gt;Right away we get into trouble here.  There is a very big difference between these two invocations, which I actually completely forgot about just now after spending four hours writing about Python and&amp;nbsp;Lua:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="linenos"&gt;1&lt;/span&gt;&lt;span class="kd"&gt;let&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;vec&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="nx"&gt;Vector&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mf"&gt;3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;4&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="linenos"&gt;2&lt;/span&gt;&lt;span class="kd"&gt;let&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;vec&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="ow"&gt;new&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;Vector&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mf"&gt;3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;4&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;The first calls the&amp;nbsp;function &lt;code&gt;Vector&lt;/code&gt;.  It assigns some properties&amp;nbsp;to &lt;code&gt;this&lt;/code&gt;, which here is going to&amp;nbsp;be &lt;code&gt;window&lt;/code&gt;, so now you have a&amp;nbsp;global &lt;code&gt;x&lt;/code&gt; and &lt;code&gt;y&lt;/code&gt;.  It then returns nothing,&amp;nbsp;so &lt;code&gt;vec&lt;/code&gt; is &lt;code&gt;undefined&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;The second&amp;nbsp;calls &lt;code&gt;Vector&lt;/code&gt; with &lt;code&gt;this&lt;/code&gt; set to a new empty object, then evaluates to that object.  The result is what you&amp;#8217;d actually&amp;nbsp;expect.&lt;/p&gt;
&lt;p&gt;(You can detect this situation with the&amp;nbsp;strange &lt;code&gt;new.target&lt;/code&gt; expression, but I have never once remembered to do&amp;nbsp;so.)&lt;/p&gt;
&lt;p&gt;From here, we have true, honest-to-god, first-class prototypical inheritance.  The word &amp;#8220;prototype&amp;#8221; is even &lt;em&gt;right there&lt;/em&gt;.  When you write&amp;nbsp;this:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="linenos"&gt;1&lt;/span&gt;&lt;span class="nx"&gt;vec&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;dot&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;vec2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;JavaScript will look&amp;nbsp;for &lt;code&gt;dot&lt;/code&gt; on &lt;code&gt;vec&lt;/code&gt; and (presumably) not find it.  It then&amp;nbsp;consults &lt;code&gt;vec&lt;/code&gt;&lt;span class="quo"&gt;&amp;#8216;&lt;/span&gt;s &lt;em&gt;prototype&lt;/em&gt;, an object you can see for yourself by&amp;nbsp;using &lt;code&gt;Object.getPrototypeOf()&lt;/code&gt;.&amp;nbsp;Since &lt;code&gt;vec&lt;/code&gt; is&amp;nbsp;a &lt;code&gt;Vector&lt;/code&gt;, its prototype&amp;nbsp;is &lt;code&gt;Vector.prototype&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;I stress&amp;nbsp;that &lt;code&gt;Vector.prototype&lt;/code&gt; &lt;strong&gt;is not&lt;/strong&gt; the prototype&amp;nbsp;for &lt;code&gt;Vector&lt;/code&gt;.  It&amp;#8217;s the prototype for &lt;em&gt;instances&amp;nbsp;of&lt;/em&gt; &lt;code&gt;Vector&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;(I say &amp;#8220;instance&amp;#8221;, but the true type&amp;nbsp;of &lt;code&gt;vec&lt;/code&gt; here is still just &lt;em&gt;object&lt;/em&gt;.  If you want to&amp;nbsp;find &lt;code&gt;Vector&lt;/code&gt;, it&amp;#8217;s automatically assigned to&amp;nbsp;the &lt;code&gt;constructor&lt;/code&gt; property of its&amp;nbsp;own &lt;code&gt;prototype&lt;/code&gt;, so it&amp;#8217;s available&amp;nbsp;as &lt;code&gt;vec.constructor&lt;/code&gt;.)&lt;/p&gt;
&lt;p&gt;Of&amp;nbsp;course, &lt;code&gt;Vector.prototype&lt;/code&gt; can itself have a prototype, in which case the process would continue&amp;nbsp;if &lt;code&gt;dot&lt;/code&gt; were not found.  A common (and, arguably, very bad) way to simulate single inheritance is to&amp;nbsp;set &lt;code&gt;Class.prototype&lt;/code&gt; to an &lt;em&gt;instance of&lt;/em&gt; a superclass to get the prototype right, then tack on the methods&amp;nbsp;for &lt;code&gt;Class&lt;/code&gt;.  Nowadays we can&amp;nbsp;do &lt;code&gt;Object.create(Superclass.prototype)&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Now that I&amp;#8217;ve been through Python and Lua, though, this isn&amp;#8217;t particularly surprising.  I kinda spoiled&amp;nbsp;it.&lt;/p&gt;
&lt;p&gt;I suppose one difference in JavaScript is that you can tack arbitrary attributes directly&amp;nbsp;onto &lt;code&gt;Vector&lt;/code&gt; all you like, and they will remain &lt;em&gt;invisible&lt;/em&gt; to instances since they aren&amp;#8217;t in the prototype chain.  This is kind of backwards from Lua, where you can squirrel stuff away in the &lt;em&gt;metatable&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;Another difference is that &lt;em&gt;every single object&lt;/em&gt; in JavaScript has a bunch of properties already tacked on — the ones&amp;nbsp;in &lt;code&gt;Object.prototype&lt;/code&gt;.  Every object (and by &amp;#8220;object&amp;#8221; I mean any mapping) has a prototype, and that prototype defaults&amp;nbsp;to &lt;code&gt;Object.prototype&lt;/code&gt;, and it has a bunch of ancient junk&amp;nbsp;like &lt;code&gt;isPrototypeOf&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;(Nit: it&amp;#8217;s possible to explicitly create an object with no prototype&amp;nbsp;via &lt;code&gt;Object.create(null)&lt;/code&gt;.)&lt;/p&gt;
&lt;p&gt;Like Lua, and unlike Python, JavaScript doesn&amp;#8217;t distinguish between keys found on an object and keys found via a prototype.  Properties can be defined on prototypes&amp;nbsp;with &lt;code&gt;Object.defineProperty()&lt;/code&gt;, but that works just as well directly on an object, too.  JavaScript doesn&amp;#8217;t have a &lt;em&gt;lot&lt;/em&gt; of operator overloading, but some things&amp;nbsp;like &lt;code&gt;Symbol.iterator&lt;/code&gt; also work on both objects and&amp;nbsp;prototypes.&lt;/p&gt;
&lt;h3 id="about-this"&gt;&lt;a class="toclink" href="#about-this"&gt;About this&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;You may, at this point, be wondering&amp;nbsp;what &lt;code&gt;this&lt;/code&gt; &lt;em&gt;is&lt;/em&gt;.  Unlike Lua and Python (and the last language&amp;nbsp;below), &lt;code&gt;this&lt;/code&gt; is a special built-in value — a &lt;em&gt;context&lt;/em&gt; value, invisibly passed for every function&amp;nbsp;call.&lt;/p&gt;
&lt;p&gt;It&amp;#8217;s determined by where the function came from.  If the function was the result of an attribute lookup,&amp;nbsp;then &lt;code&gt;this&lt;/code&gt; is set to the object containing that attribute.&amp;nbsp;Otherwise, &lt;code&gt;this&lt;/code&gt; is set to the global&amp;nbsp;object, &lt;code&gt;window&lt;/code&gt;.  (You can also&amp;nbsp;set &lt;code&gt;this&lt;/code&gt; to whatever you want via&amp;nbsp;the &lt;code&gt;call&lt;/code&gt; method on&amp;nbsp;functions.)&lt;/p&gt;
&lt;p&gt;This decision is made &lt;em&gt;lexically&lt;/em&gt;, i.e. from the literal source code as written.  There are no Python-style bound methods.  In other&amp;nbsp;words:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="linenos"&gt;1&lt;/span&gt;&lt;span class="c1"&gt;// this = obj&lt;/span&gt;
&lt;span class="linenos"&gt;2&lt;/span&gt;&lt;span class="nx"&gt;obj&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;method&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="linenos"&gt;3&lt;/span&gt;&lt;span class="c1"&gt;// this = window&lt;/span&gt;
&lt;span class="linenos"&gt;4&lt;/span&gt;&lt;span class="kd"&gt;let&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;meth&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="nx"&gt;obj&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;method&lt;/span&gt;
&lt;span class="linenos"&gt;5&lt;/span&gt;&lt;span class="nx"&gt;meth&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Also,&amp;nbsp;because &lt;code&gt;this&lt;/code&gt; is reassigned on &lt;em&gt;every&lt;/em&gt; function call, it cannot be meaningfully closed over, which makes using closures within methods incredibly annoying.  The old approach was to&amp;nbsp;assign &lt;code&gt;this&lt;/code&gt; to some other regular name&amp;nbsp;like &lt;code&gt;self&lt;/code&gt; (which got syntax highlighting since it&amp;#8217;s also a built-in name in browsers); then we&amp;nbsp;got &lt;code&gt;Function.bind&lt;/code&gt;, which produced a callable thing with a fixed context value, which was kind of nice; and now finally we have arrow functions, which explicitly close over the&amp;nbsp;current &lt;code&gt;this&lt;/code&gt; when they&amp;#8217;re defined and don&amp;#8217;t change it when called.&amp;nbsp;Phew.&lt;/p&gt;
&lt;h3 id="class-syntax"&gt;&lt;a class="toclink" href="#class-syntax"&gt;Class syntax&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;I already showed class syntax, and it&amp;#8217;s really just one big macro for doing all the prototype stuff The Right Way.  It even prevents you from calling the type&amp;nbsp;without &lt;code&gt;new&lt;/code&gt;.  The underlying model is exactly the same, and you can inspect all the&amp;nbsp;parts.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="linenos"&gt; 1&lt;/span&gt;&lt;span class="kd"&gt;class&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;Vector&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="p"&gt;}&lt;/span&gt;
&lt;span class="linenos"&gt; 2&lt;/span&gt;
&lt;span class="linenos"&gt; 3&lt;/span&gt;&lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;Vector&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;prototype&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="c1"&gt;// { dot: ..., magnitude: ..., ... }&lt;/span&gt;
&lt;span class="linenos"&gt; 4&lt;/span&gt;&lt;span class="kd"&gt;let&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;vec&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="ow"&gt;new&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;Vector&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mf"&gt;3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;4&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="linenos"&gt; 5&lt;/span&gt;&lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;Object&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;getPrototypeOf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;vec&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 as Vector.prototype&lt;/span&gt;
&lt;span class="linenos"&gt; 6&lt;/span&gt;
&lt;span class="linenos"&gt; 7&lt;/span&gt;&lt;span class="c1"&gt;// i don&amp;#39;t know why you would subclass vector but let&amp;#39;s roll with it&lt;/span&gt;
&lt;span class="linenos"&gt; 8&lt;/span&gt;&lt;span class="kd"&gt;class&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;Vectest&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;extends&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;Vector&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="p"&gt;}&lt;/span&gt;
&lt;span class="linenos"&gt; 9&lt;/span&gt;
&lt;span class="linenos"&gt;10&lt;/span&gt;&lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;Vectest&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;prototype&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="c1"&gt;// { ... }&lt;/span&gt;
&lt;span class="linenos"&gt;11&lt;/span&gt;&lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;Object&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;getPrototypeOf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;Vectest&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;prototype&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 as Vector.prototype&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Alas, class syntax has a couple shortcomings.  You can&amp;#8217;t use&amp;nbsp;the &lt;code&gt;class&lt;/code&gt; block to assign arbitrary &lt;em&gt;data&lt;/em&gt; to either the type object or the prototype — apparently it was deemed too confusing that mutations would be shared among instances.  Which…  is…  how prototypes work.  How Python works.  How JavaScript itself, one of the most popular languages of all time, has worked for &lt;strong&gt;twenty-two years&lt;/strong&gt;.&amp;nbsp;Argh.&lt;/p&gt;
&lt;p&gt;You can still do whatever assignment you want &lt;em&gt;outside&lt;/em&gt; of the class block, of course.  It&amp;#8217;s just a little ugly, and not something I&amp;#8217;d think to look for with a sugary&amp;nbsp;class.&lt;/p&gt;
&lt;p&gt;A more subtle result of this behavior is that&amp;nbsp;a &lt;code&gt;class&lt;/code&gt; block isn&amp;#8217;t &lt;em&gt;quite&lt;/em&gt; the same syntax as an object literal.  The check for data isn&amp;#8217;t a runtime&amp;nbsp;thing; &lt;code&gt;class Foo { x: 3 }&lt;/code&gt; fails to &lt;em&gt;parse&lt;/em&gt;.  So JavaScript now has two &lt;em&gt;largely but not entirely identical&lt;/em&gt; styles of key/value&amp;nbsp;block.&lt;/p&gt;
&lt;h3 id="attribute-access"&gt;&lt;a class="toclink" href="#attribute-access"&gt;Attribute access&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Here&amp;#8217;s where things start to come apart at the seams, just a little&amp;nbsp;bit.&lt;/p&gt;
&lt;p&gt;JavaScript doesn&amp;#8217;t really have an attribute &lt;em&gt;protocol&lt;/em&gt;.  Instead, it has two&amp;#8230;  extension points, I&amp;nbsp;suppose.&lt;/p&gt;
&lt;p&gt;One&amp;nbsp;is &lt;code&gt;Object.defineProperty&lt;/code&gt;, seen above.  For common cases, there&amp;#8217;s also&amp;nbsp;the &lt;code&gt;get&lt;/code&gt; syntax inside a property literal, which does the same thing.  But unlike&amp;nbsp;Python&amp;#8217;s &lt;code&gt;@property&lt;/code&gt;, these aren&amp;#8217;t wrappers around some simple primitives; they &lt;em&gt;are&lt;/em&gt; the primitives.  JavaScript is the only language of these four to have &amp;#8220;property that runs code on access&amp;#8221; as a completely separate first-class&amp;nbsp;concept.&lt;/p&gt;
&lt;p&gt;If you want to intercept &lt;em&gt;arbitrary&lt;/em&gt; attribute access (and some kinds of operators), there&amp;#8217;s a &lt;em&gt;completely different&lt;/em&gt; primitive: the &lt;a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Proxy"&gt;&lt;code&gt;Proxy&lt;/code&gt;&lt;/a&gt; type.  It doesn&amp;#8217;t let you intercept attribute access or operators; instead, it produces a wrapper object that supports interception and defers to the wrapped object by&amp;nbsp;default.&lt;/p&gt;
&lt;p&gt;It&amp;#8217;s cool to see composition used in this way, but also, extremely weird.  If you want to make your own type that&amp;nbsp;overloads &lt;code&gt;in&lt;/code&gt; or calling, you &lt;em&gt;have&lt;/em&gt; to return&amp;nbsp;a &lt;code&gt;Proxy&lt;/code&gt; that wraps your own type, rather than actually returning your own type.  It&amp;#8217;s workable, though — constructors can return whatever object they want, and proxies are transparent enough&amp;nbsp;that &lt;code&gt;instanceof&lt;/code&gt; already behaves correctly.  (If it didn&amp;#8217;t, you could&amp;nbsp;customize &lt;code&gt;instanceof&lt;/code&gt; with &lt;code&gt;Symbol.hasInstance&lt;/code&gt; — which is really operator overloading, implement yet another completely different&amp;nbsp;way.)&lt;/p&gt;
&lt;p&gt;I know the design here is a result of legacy and speed — if any object could intercept &lt;em&gt;all&lt;/em&gt; attribute access, then &lt;em&gt;all&lt;/em&gt; attribute access would be slowed down everywhere.  Fair enough.  It still leaves the surface area of the language a bit…&amp;nbsp;bumpy?&lt;/p&gt;
&lt;h3 id="the-javascript-philosophy"&gt;&lt;a class="toclink" href="#the-javascript-philosophy"&gt;The JavaScript philosophy&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;It&amp;#8217;s a little hard to tell.  The original idea of prototypes was interesting, but it was hidden behind some &lt;em&gt;very&lt;/em&gt; awkward syntax.  Since then, we&amp;#8217;ve gotten a bunch of extra features awkwardly bolted on to reflect the wildly varied things the built-in types and &lt;span class="caps"&gt;DOM&lt;/span&gt; &lt;span class="caps"&gt;API&lt;/span&gt; were already doing.  We&amp;nbsp;have &lt;code&gt;class&lt;/code&gt; syntax, but it&amp;#8217;s been explicitly designed to avoid exposing the &lt;em&gt;prototype&lt;/em&gt; parts of the&amp;nbsp;model.&lt;/p&gt;
&lt;p&gt;I admit I don&amp;#8217;t do a &lt;em&gt;lot&lt;/em&gt; of heavy JavaScript, so I might just be overlooking it, but I&amp;#8217;ve seen virtually no code that makes use of any of the recent advances in object capabilities.  Forget about custom iterators or overloading call; I can&amp;#8217;t remember seeing any JavaScript in the wild that even uses properties yet.  I don&amp;#8217;t know if everyone&amp;#8217;s waiting for sufficient browser support, nobody knows about them, or nobody&amp;nbsp;cares.&lt;/p&gt;
&lt;p&gt;The model has advanced recently, but I suspect JavaScript is still shackled to its legacy of &amp;#8220;something about prototypes, I don&amp;#8217;t really get it, just copy the other code that&amp;#8217;s there&amp;#8221; as an object model.  Alas!  Prototypes are so good.&amp;nbsp;Hopefully &lt;code&gt;class&lt;/code&gt; syntax will make it a bit more accessible, as it has in&amp;nbsp;Python.&lt;/p&gt;
&lt;h2 id="perl-5"&gt;&lt;a class="toclink" href="#perl-5"&gt;Perl 5&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Perl 5 also doesn&amp;#8217;t have an object system and expects you to build your own.  But where Lua gives you two simple, powerful tools for building one, Perl 5 feels more like a puzzle with half the pieces missing.  Clearly they were going for &lt;em&gt;something&lt;/em&gt;, but they only gave you half of&amp;nbsp;it.&lt;/p&gt;
&lt;p&gt;In brief, a Perl object is a reference that has been blessed with a&amp;nbsp;package.&lt;/p&gt;
&lt;p&gt;I need to explain a few things.  Honestly, one of the biggest problems with the original Perl object setup was how many strange corners and unique jargon you had to understand just to get off the&amp;nbsp;ground.&lt;/p&gt;
&lt;p&gt;(If you want to try running any of this code, you should stick&amp;nbsp;a &lt;code&gt;use v5.26;&lt;/code&gt; as the first line.  Perl is very big on backwards compatibility, so you need to opt into breaking changes, and even the&amp;nbsp;mundane &lt;code&gt;say&lt;/code&gt; builtin is behind a feature&amp;nbsp;gate.)&lt;/p&gt;
&lt;h3 id="references"&gt;&lt;a class="toclink" href="#references"&gt;References&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;A &lt;strong&gt;reference&lt;/strong&gt; in Perl is sort of like a pointer, but its main use is very different.  See, Perl has the strange property that its data structures try &lt;em&gt;very hard&lt;/em&gt; to spill their contents all over the place.  Despite having dedicated syntax for arrays&amp;nbsp;— &lt;code&gt;@foo&lt;/code&gt; is an array variable, distinct from the single scalar&amp;nbsp;variable &lt;code&gt;$foo&lt;/code&gt; — it&amp;#8217;s actually impossible to &lt;em&gt;nest&lt;/em&gt;&amp;nbsp;arrays.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="linenos"&gt;1&lt;/span&gt;&lt;span class="k"&gt;my&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;@foo&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="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="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="linenos"&gt;2&lt;/span&gt;&lt;span class="k"&gt;my&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;@bar&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="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;@foo&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;@foo&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="linenos"&gt;3&lt;/span&gt;&lt;span class="c1"&gt;# @bar is now a flat list of eight items: 1, 2, 3, 4, 1, 2, 3, 4&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;The idea, I guess, is that an array is not &lt;em&gt;one thing&lt;/em&gt;.  It&amp;#8217;s not a container, which happens to hold multiple things; it &lt;strong&gt;is&lt;/strong&gt; multiple things.  Anywhere that expects a single value, such as an array element, cannot contain an array, because an array fundamentally &lt;em&gt;is not&lt;/em&gt; a single&amp;nbsp;value.&lt;/p&gt;
&lt;p&gt;And so we have &amp;#8220;references&amp;#8221;, which are a form of indirection, but also have the nice property that they&amp;#8217;re &lt;em&gt;single values&lt;/em&gt;.  They add containment around arrays, and in general they make working with most of Perl&amp;#8217;s primitive types much more sensible.  A reference to a variable can be taken with&amp;nbsp;the &lt;code&gt;\&lt;/code&gt; operator, or you can&amp;nbsp;use &lt;code&gt;[ ... ]&lt;/code&gt; and &lt;code&gt;{ ... }&lt;/code&gt; to directly create references to anonymous arrays or&amp;nbsp;hashes.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="linenos"&gt;1&lt;/span&gt;&lt;span class="k"&gt;my&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;@foo&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="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="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="linenos"&gt;2&lt;/span&gt;&lt;span class="k"&gt;my&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;@bar&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="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;\&lt;/span&gt;&lt;span class="nv"&gt;@foo&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="nv"&gt;@foo&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="linenos"&gt;3&lt;/span&gt;&lt;span class="c1"&gt;# @bar is now a nested list of two items: [1, 2, 3, 4], [1, 2, 3, 4]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;(Incidentally, this is the sole reason I initially abandoned Perl for Python.  Non-trivial software kinda requires nesting a lot of data structures, so you end up with references &lt;em&gt;everywhere&lt;/em&gt;, and the syntax for going back and forth between a reference and its contents is tedious and&amp;nbsp;ugly.)&lt;/p&gt;
&lt;p&gt;A Perl object must be a reference.  Perl doesn&amp;#8217;t care what &lt;em&gt;kind&lt;/em&gt; of reference — it&amp;#8217;s usually a hash reference, since hashes are a convenient place to store arbitrary properties, but it could just as well be a reference to an array, a scalar, or even a sub (i.e. function) or&amp;nbsp;filehandle.&lt;/p&gt;
&lt;p&gt;I&amp;#8217;m getting a little ahead of myself.  First, the other half: blessing and&amp;nbsp;packages.&lt;/p&gt;
&lt;h3 id="packages-and-blessing"&gt;&lt;a class="toclink" href="#packages-and-blessing"&gt;Packages and blessing&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Perl &lt;em&gt;packages&lt;/em&gt; are just namespaces.  A package looks like&amp;nbsp;this:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="linenos"&gt;1&lt;/span&gt;&lt;span class="k"&gt;package&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;Foo::Bar&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="linenos"&gt;2&lt;/span&gt;
&lt;span class="linenos"&gt;3&lt;/span&gt;&lt;span class="k"&gt;sub&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;quux&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="linenos"&gt;4&lt;/span&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;say&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;hi from quux!&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="linenos"&gt;5&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="linenos"&gt;6&lt;/span&gt;
&lt;span class="linenos"&gt;7&lt;/span&gt;&lt;span class="c1"&gt;# now Foo::Bar::quux() can be called from anywhere&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Nothing shocking, right?  It&amp;#8217;s just a named container.  A lot of the details are kind of weird, like how a package exists in some liminal quasi-value space, but the basic idea is a Bag Of&amp;nbsp;Stuff.&lt;/p&gt;
&lt;p&gt;The final piece is &amp;#8220;blessing,&amp;#8221; which is Perl&amp;#8217;s funny name for binding a package to a reference.  A very basic class might look like&amp;nbsp;this:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="linenos"&gt; 1&lt;/span&gt;&lt;span class="k"&gt;package&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;Vector&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="linenos"&gt; 2&lt;/span&gt;
&lt;span class="linenos"&gt; 3&lt;/span&gt;&lt;span class="c1"&gt;# the name &amp;#39;new&amp;#39; is convention, not special&lt;/span&gt;
&lt;span class="linenos"&gt; 4&lt;/span&gt;&lt;span class="k"&gt;sub&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="linenos"&gt; 5&lt;/span&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="c1"&gt;# perl argument passing is weird, don&amp;#39;t ask&lt;/span&gt;
&lt;span class="linenos"&gt; 6&lt;/span&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;my&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$class&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;$x&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;$y&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="nv"&gt;@_&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="linenos"&gt; 7&lt;/span&gt;
&lt;span class="linenos"&gt; 8&lt;/span&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="c1"&gt;# create the object itself -- here, unusually, an array reference makes sense&lt;/span&gt;
&lt;span class="linenos"&gt; 9&lt;/span&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;my&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;$self&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="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;$x&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;$y&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;
&lt;span class="linenos"&gt;10&lt;/span&gt;
&lt;span class="linenos"&gt;11&lt;/span&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="c1"&gt;# associate the package with that reference&lt;/span&gt;
&lt;span class="linenos"&gt;12&lt;/span&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="c1"&gt;# note that $class here is just the regular string, &amp;#39;Vector&amp;#39;&lt;/span&gt;
&lt;span class="linenos"&gt;13&lt;/span&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nb"&gt;bless&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;$self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;$class&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="linenos"&gt;14&lt;/span&gt;
&lt;span class="linenos"&gt;15&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="nv"&gt;$self&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="linenos"&gt;16&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="linenos"&gt;17&lt;/span&gt;
&lt;span class="linenos"&gt;18&lt;/span&gt;&lt;span class="k"&gt;sub&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;x&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="linenos"&gt;19&lt;/span&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;my&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$self&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="nv"&gt;@_&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="linenos"&gt;20&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="nv"&gt;$self&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&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="linenos"&gt;21&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="linenos"&gt;22&lt;/span&gt;
&lt;span class="linenos"&gt;23&lt;/span&gt;&lt;span class="k"&gt;sub&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;y&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="linenos"&gt;24&lt;/span&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;my&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$self&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="nv"&gt;@_&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="linenos"&gt;25&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="nv"&gt;$self&lt;/span&gt;&lt;span class="o"&gt;-&amp;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="linenos"&gt;26&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="linenos"&gt;27&lt;/span&gt;
&lt;span class="linenos"&gt;28&lt;/span&gt;&lt;span class="k"&gt;sub&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;magnitude&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="linenos"&gt;29&lt;/span&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;my&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$self&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="nv"&gt;@_&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="linenos"&gt;30&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;sqrt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$self&lt;/span&gt;&lt;span class="o"&gt;-&amp;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;**&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;2&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="nv"&gt;$self&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;y&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;2&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="linenos"&gt;31&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="linenos"&gt;32&lt;/span&gt;
&lt;span class="linenos"&gt;33&lt;/span&gt;&lt;span class="c1"&gt;# switch back to the &amp;quot;default&amp;quot; package&lt;/span&gt;
&lt;span class="linenos"&gt;34&lt;/span&gt;&lt;span class="k"&gt;package&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;main&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="linenos"&gt;35&lt;/span&gt;
&lt;span class="linenos"&gt;36&lt;/span&gt;&lt;span class="c1"&gt;# -&amp;gt; is method call syntax, which passes the invocant as the first argument;&lt;/span&gt;
&lt;span class="linenos"&gt;37&lt;/span&gt;&lt;span class="c1"&gt;# for a package, that&amp;#39;s just the package name&lt;/span&gt;
&lt;span class="linenos"&gt;38&lt;/span&gt;&lt;span class="k"&gt;my&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;$vec&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="nn"&gt;Vector&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="k"&gt;new&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="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="linenos"&gt;39&lt;/span&gt;&lt;span class="n"&gt;say&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;$vec&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;magnitude&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="c1"&gt;# 5&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;A few things of note here.&amp;nbsp;First, &lt;code&gt;$self-&amp;gt;[0]&lt;/code&gt; has nothing to do with objects; it&amp;#8217;s normal syntax for getting the value of a index 0 out of an array reference&amp;nbsp;called &lt;code&gt;$self&lt;/code&gt;.  (Most classes are based on hashrefs and would&amp;nbsp;use &lt;code&gt;$self-&amp;gt;{value}&lt;/code&gt; instead.)  A blessed reference &lt;em&gt;is still a reference&lt;/em&gt; and can be treated like&amp;nbsp;one.&lt;/p&gt;
&lt;p&gt;In&amp;nbsp;general, &lt;code&gt;-&amp;gt;&lt;/code&gt; is Perl&amp;#8217;s dereferencey operator, but its exact behavior depends on what follows.  If it&amp;#8217;s followed by brackets, then it&amp;#8217;ll apply the brackets to the thing in the&amp;nbsp;reference: &lt;code&gt;-&amp;gt;{}&lt;/code&gt; to index a hash&amp;nbsp;reference, &lt;code&gt;-&amp;gt;[]&lt;/code&gt; to index an array reference,&amp;nbsp;and &lt;code&gt;-&amp;gt;()&lt;/code&gt; to call a function&amp;nbsp;reference.&lt;/p&gt;
&lt;p&gt;But&amp;nbsp;if &lt;code&gt;-&amp;gt;&lt;/code&gt; is followed by an identifier, then it&amp;#8217;s a &lt;em&gt;method call&lt;/em&gt;.  For packages, that means calling a function in the package and passing the package name as the first argument.  For objects — blessed references — that means calling a function in the &lt;em&gt;associated&lt;/em&gt; package and passing the object as the first&amp;nbsp;argument.&lt;/p&gt;
&lt;p&gt;This is a little weird!  A blessed reference is a superposition of two things: its normal reference behavior, and some &lt;em&gt;completely orthogonal&lt;/em&gt; object behavior.  Also, object behavior has no notion of methods vs data; it only knows about methods.  Perl lets you omit parentheses in a lot of places, including when calling a method with no arguments,&amp;nbsp;so &lt;code&gt;$vec-&amp;gt;magnitude&lt;/code&gt; is&amp;nbsp;really &lt;code&gt;$vec-&amp;gt;magnitude()&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Perl&amp;#8217;s blessing bears some similarities to Lua&amp;#8217;s metatables, but ultimately Perl is much closer to Ruby&amp;#8217;s &amp;#8220;message passing&amp;#8221; approach than the above three languages&amp;#8217; approaches of &amp;#8220;get me something and maybe it&amp;#8217;ll be callable&amp;#8221;.  (But this is no surprise — Ruby is a spiritual successor to Perl&amp;nbsp;5.)&lt;/p&gt;
&lt;p&gt;All of this leads to one little wrinkle: how do you actually expose data?  Above, I had to&amp;nbsp;write &lt;code&gt;x&lt;/code&gt; and &lt;code&gt;y&lt;/code&gt; methods.  Am I supposed to do that for every single attribute on my&amp;nbsp;type?&lt;/p&gt;
&lt;p&gt;Yes!  But don&amp;#8217;t worry, there are third-party modules to help with this incredibly fundamental task.  Take &lt;a href="http://search.cpan.org/~kasei/Class-Accessor-0.51/lib/Class/Accessor.pm"&gt;&lt;code&gt;Class::Accessor::Fast&lt;/code&gt;&lt;/a&gt;, so named because it&amp;#8217;s faster&amp;nbsp;than &lt;code&gt;Class::Accessor&lt;/code&gt;:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="linenos"&gt;1&lt;/span&gt;&lt;span class="k"&gt;package&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;Foo&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="linenos"&gt;2&lt;/span&gt;&lt;span class="k"&gt;use&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;base&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="sx"&gt;qw(Class::Accessor::Fast)&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="linenos"&gt;3&lt;/span&gt;&lt;span class="nn"&gt;__PACKAGE__&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;mk_accessors&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sx"&gt;qw(fred wilma barney)&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;(&lt;code&gt;__PACKAGE__&lt;/code&gt; is the lexical name of the current&amp;nbsp;package; &lt;code&gt;qw(...)&lt;/code&gt; is a list literal that splits its contents on&amp;nbsp;whitespace.)&lt;/p&gt;
&lt;p&gt;This assumes you&amp;#8217;re using a hashref with keys of the same names as the&amp;nbsp;attributes.  &lt;code&gt;$obj-&amp;gt;fred&lt;/code&gt; will return&amp;nbsp;the &lt;code&gt;fred&lt;/code&gt; key from your hashref,&amp;nbsp;and &lt;code&gt;$obj-&amp;gt;fred(4)&lt;/code&gt; will change it to&amp;nbsp;4.&lt;/p&gt;
&lt;p&gt;You also, somewhat bizarrely, have to &lt;em&gt;inherit&amp;nbsp;from&lt;/em&gt; &lt;code&gt;Class::Accessor::Fast&lt;/code&gt;.  Speaking of&amp;nbsp;which,&lt;/p&gt;
&lt;h3 id="inheritance"&gt;&lt;a class="toclink" href="#inheritance"&gt;Inheritance&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Inheritance is done by populating the&amp;nbsp;package-global &lt;code&gt;@ISA&lt;/code&gt; array with some number of (string) names of parent packages.  Most code instead opts to&amp;nbsp;write &lt;code&gt;use base ...;&lt;/code&gt;, which does the same thing.  Or, more&amp;nbsp;commonly, &lt;code&gt;use parent ...;&lt;/code&gt;, which…  also…  does the same&amp;nbsp;thing.&lt;/p&gt;
&lt;p&gt;Every package implicitly inherits&amp;nbsp;from &lt;code&gt;UNIVERSAL&lt;/code&gt;, which can be freely modified by Perl&amp;nbsp;code.&lt;/p&gt;
&lt;p&gt;A method can call its superclass method with&amp;nbsp;the &lt;code&gt;SUPER::&lt;/code&gt; pseudo-package:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="linenos"&gt;1&lt;/span&gt;&lt;span class="k"&gt;sub&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;foo&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="linenos"&gt;2&lt;/span&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;my&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$self&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="nv"&gt;@_&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="linenos"&gt;3&lt;/span&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nv"&gt;$self&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nn"&gt;SUPER::&lt;/span&gt;&lt;span class="n"&gt;foo&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="linenos"&gt;4&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;However, this does a depth-first search, which means it almost certainly does the wrong thing when faced with multiple inheritance.  For a while the accepted solution involved a third-party module, but Perl eventually grew an alternative you have to opt into: C3, which may be more familiar to you as &lt;a href="https://perldoc.perl.org/mro.html#How-does-C3-work"&gt;the order Python uses&lt;/a&gt;.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="linenos"&gt;1&lt;/span&gt;&lt;span class="k"&gt;use&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;mro&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;#39;c3&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="linenos"&gt;2&lt;/span&gt;
&lt;span class="linenos"&gt;3&lt;/span&gt;&lt;span class="k"&gt;sub&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;foo&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="linenos"&gt;4&lt;/span&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;my&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$self&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="nv"&gt;@_&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="linenos"&gt;5&lt;/span&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nv"&gt;$self&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="k"&gt;next&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;method&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="linenos"&gt;6&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Offhand, I&amp;#8217;m not actually sure&amp;nbsp;how &lt;code&gt;next::method&lt;/code&gt; works, seeing as it was originally implemented in pure Perl code.  I suspect it involves peeking at the caller&amp;#8217;s stack frame.  If so, then this is a very different style of customizability from e.g. Python — the &lt;span class="caps"&gt;MRO&lt;/span&gt; was never &lt;em&gt;intended&lt;/em&gt; to be pluggable, and the use of a special pseudo-package means it &lt;em&gt;isn&amp;#8217;t&lt;/em&gt; really, but someone was determined enough to make it happen&amp;nbsp;anyway.&lt;/p&gt;
&lt;h3 id="operator-overloading-and-whatnot"&gt;&lt;a class="toclink" href="#operator-overloading-and-whatnot"&gt;Operator overloading and whatnot&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Operator overloading looks a little weird, though really it&amp;#8217;s pretty standard&amp;nbsp;Perl.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="linenos"&gt;1&lt;/span&gt;&lt;span class="k"&gt;package&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;MyClass&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="linenos"&gt;2&lt;/span&gt;
&lt;span class="linenos"&gt;3&lt;/span&gt;&lt;span class="k"&gt;use&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;overload&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;#39;+&amp;#39;&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="o"&gt;\&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;_add&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="linenos"&gt;4&lt;/span&gt;
&lt;span class="linenos"&gt;5&lt;/span&gt;&lt;span class="k"&gt;sub&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;_add&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="linenos"&gt;6&lt;/span&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;my&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;$other&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;$swap&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="nv"&gt;@_&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="linenos"&gt;7&lt;/span&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="o"&gt;...&lt;/span&gt;
&lt;span class="linenos"&gt;8&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;&lt;a href="https://perldoc.perl.org/overload.html"&gt;&lt;code&gt;use overload&lt;/code&gt;&lt;/a&gt; here is a &lt;em&gt;pragma&lt;/em&gt;, where &amp;#8220;pragma&amp;#8221; means &amp;#8220;regular-ass module that does some wizardry when&amp;nbsp;imported&amp;#8221;.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;\&amp;amp;_add&lt;/code&gt; is how you get a reference to&amp;nbsp;the &lt;code&gt;_add&lt;/code&gt; sub so you can pass it to&amp;nbsp;the &lt;code&gt;overload&lt;/code&gt; module.  If you just&amp;nbsp;said &lt;code&gt;&amp;amp;_add&lt;/code&gt; or &lt;code&gt;_add&lt;/code&gt;, that would &lt;em&gt;call&lt;/em&gt;&amp;nbsp;it.&lt;/p&gt;
&lt;p&gt;And that&amp;#8217;s it; you just pass a map of operators to functions to this built-in module.  No worry about name clashes or pollution, which is pretty nice.  You don&amp;#8217;t even have to give references to functions that live in the package, if you don&amp;#8217;t want them to clog your namespace; you could put them in another package, or even inline them&amp;nbsp;anonymously.&lt;/p&gt;
&lt;p&gt;One especially interesting thing is that Perl lets you overload &lt;em&gt;every&lt;/em&gt; operator.  Perl has a lot of operators.  It considers some math builtins&amp;nbsp;like &lt;code&gt;sqrt&lt;/code&gt; and trig functions to be operators, or at least operator-y enough that you can overload them.  You can also overload the &amp;#8220;file test&amp;#8221; operators, such&amp;nbsp;as &lt;code&gt;-e $path&lt;/code&gt; to test whether a file exists.  You can overload conversions, including implicit conversion to a &lt;em&gt;regex&lt;/em&gt;.  And most fascinating to me, you can overload &lt;em&gt;dereferencing&lt;/em&gt; — that is, the thing Perl does when you&amp;nbsp;say &lt;code&gt;$hashref-&amp;gt;{key}&lt;/code&gt; to get at the underlying hash.  So a single object could pretend to be references of multiple different types, including a subref to implement callability.&amp;nbsp;Neat.&lt;/p&gt;
&lt;p&gt;Somewhat related: you can overload basic operators (indexing, etc.) on basic &lt;em&gt;types&lt;/em&gt; (not references!) with the &lt;a href="https://perldoc.perl.org/functions/tie.html"&gt;&lt;code&gt;tie&lt;/code&gt;&lt;/a&gt; function, which is designed completely differently and looks for methods with fixed names.  Go&amp;nbsp;figure.&lt;/p&gt;
&lt;p&gt;You can intercept calls to nonexistent methods by implementing a function&amp;nbsp;called &lt;code&gt;AUTOLOAD&lt;/code&gt;, within which&amp;nbsp;the &lt;code&gt;$AUTOLOAD&lt;/code&gt; global will contain the name of the method being called.  Originally this feature was, I think, intended for loading binary components or large libraries on-the-fly only when needed, hence the name.  Offhand I&amp;#8217;m not sure I ever saw it used the&amp;nbsp;way &lt;code&gt;__getattr__&lt;/code&gt; is used in&amp;nbsp;Python.&lt;/p&gt;
&lt;p&gt;Is there a way to intercept &lt;em&gt;all&lt;/em&gt; method calls?  I don&amp;#8217;t think so, but it &lt;em&gt;is&lt;/em&gt; Perl, so I must be forgetting&amp;nbsp;something.&lt;/p&gt;
&lt;h3 id="actually-no-one-does-this-any-more"&gt;&lt;a class="toclink" href="#actually-no-one-does-this-any-more"&gt;Actually no one does this any more&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Like a decade ago, a council of elder sages sat down and put together a whole whizbang system that covers all of it: &lt;a href="http://search.cpan.org/~ether/Moose-2.2009/lib/Moose.pm"&gt;Moose&lt;/a&gt;.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="linenos"&gt; 1&lt;/span&gt;&lt;span class="k"&gt;package&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;Vector&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="linenos"&gt; 2&lt;/span&gt;&lt;span class="k"&gt;use&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;Moose&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="linenos"&gt; 3&lt;/span&gt;
&lt;span class="linenos"&gt; 4&lt;/span&gt;&lt;span class="n"&gt;has&lt;/span&gt;&lt;span class="w"&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;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;is&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="s"&gt;&amp;#39;rw&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;isa&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="s"&gt;&amp;#39;Int&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="linenos"&gt; 5&lt;/span&gt;&lt;span class="n"&gt;has&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;y&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="n"&gt;is&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="s"&gt;&amp;#39;rw&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;isa&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="s"&gt;&amp;#39;Int&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="linenos"&gt; 6&lt;/span&gt;
&lt;span class="linenos"&gt; 7&lt;/span&gt;&lt;span class="k"&gt;sub&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;magnitude&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="linenos"&gt; 8&lt;/span&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;my&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$self&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="nv"&gt;@_&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="linenos"&gt; 9&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;sqrt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$self&lt;/span&gt;&lt;span class="o"&gt;-&amp;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;**&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;2&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="nv"&gt;$self&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;y&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;2&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="linenos"&gt;10&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Moose has its own way to do pretty much everything, and it&amp;#8217;s all built on the same primitives.  Moose also adds &lt;em&gt;metaclasses&lt;/em&gt;, somehow, despite that the underlying model doesn&amp;#8217;t actually support them?  I&amp;#8217;m not entirely sure how they managed that, but I do remember doing some class introspection with Moose and it was &lt;em&gt;much&lt;/em&gt; nicer than the built-in&amp;nbsp;way.&lt;/p&gt;
&lt;p&gt;(If you&amp;#8217;re wondering, the built-in way begins with looking at the hash&amp;nbsp;called &lt;code&gt;%Vector::&lt;/code&gt;.  No, that&amp;#8217;s not a&amp;nbsp;typo.)&lt;/p&gt;
&lt;p&gt;I really cannot stress enough just &lt;em&gt;how much stuff&lt;/em&gt; Moose does, but I don&amp;#8217;t want to delve into it here since Moose itself is not actually the language&amp;nbsp;model.&lt;/p&gt;
&lt;h3 id="the-perl-philosophy"&gt;&lt;a class="toclink" href="#the-perl-philosophy"&gt;The Perl philosophy&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;I hope you can see what I meant with what I first said about Perl, now.  It has multiple inheritance with an &lt;span class="caps"&gt;MRO&lt;/span&gt;, but uses the wrong one by default.  It has extensive operator overloading, which looks nothing like how inheritance works, and also some of it uses a totally different mechanism with special method names instead.  It only understands methods, not data, leaving you to figure out accessors by&amp;nbsp;hand.&lt;/p&gt;
&lt;p&gt;There&amp;#8217;s 70% of an object system here with a clear general design it was gunning for, but none of the pieces really look anything like each other.  It&amp;#8217;s weird, in a distinctly Perl&amp;nbsp;way.&lt;/p&gt;
&lt;p&gt;The result is certainly flexible, at least!  It&amp;#8217;s especially cool that you can use whatever kind of reference you want for storage, though even as I say that, I acknowledge it&amp;#8217;s no different from simply&amp;nbsp;subclassing &lt;code&gt;list&lt;/code&gt; or something in Python.  It &lt;em&gt;feels&lt;/em&gt; different in Perl, but maybe only because it &lt;em&gt;looks&lt;/em&gt; so&amp;nbsp;different.&lt;/p&gt;
&lt;p&gt;I haven&amp;#8217;t written much Perl in a long time, so I don&amp;#8217;t know what the community is like any more.  Moose was already ubiquitous when I left, which you&amp;#8217;d think would let me say &amp;#8220;the community mostly focuses on the stuff Moose can do&amp;#8221; — but even a decade ago, Moose could already do far more than I had ever seen done by hand in Perl.  It&amp;#8217;s always made a big deal out of roles (read: interfaces), for instance, despite that I&amp;#8217;d never seen anyone care about them in Perl before Moose came along.  Maybe their presence in Moose has made them more popular?  Who&amp;nbsp;knows.&lt;/p&gt;
&lt;p&gt;Also, I wrote Perl &lt;em&gt;seriously&lt;/em&gt;, but in the intervening years I&amp;#8217;ve only encountered people who only ever used Perl for one-offs.  Maybe it&amp;#8217;ll come as a surprise to a lot of readers that Perl has an object model &lt;em&gt;at all&lt;/em&gt;.&lt;/p&gt;
&lt;h2 id="end"&gt;&lt;a class="toclink" href="#end"&gt;End&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Well, that was fun!  I hope any of that made&amp;nbsp;sense.&lt;/p&gt;
&lt;p&gt;Special mention goes to Rust, which doesn&amp;#8217;t have an object model you can fiddle with at runtime, but &lt;em&gt;does&lt;/em&gt; do things &lt;a href="https://doc.rust-lang.org/book/second-edition/ch10-02-traits.html"&gt;a little differently&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;It&amp;#8217;s been &lt;em&gt;really&lt;/em&gt; interesting thinking about how tiny differences make a huge impact on what people do in practice.  Take the choice of storage in Perl versus Python.  Perl&amp;#8217;s massively&amp;nbsp;common &lt;code&gt;URI&lt;/code&gt; class uses &lt;em&gt;a string&lt;/em&gt; as the storage, nothing else; I haven&amp;#8217;t seen anything like that in Python aside&amp;nbsp;from &lt;code&gt;markupsafe&lt;/code&gt;, which is specifically designed as a string type.  I would guess this is partly because Perl &lt;em&gt;makes you choose&lt;/em&gt; — using a hashref is an obvious default, but you have to make that choice one way or the other.  In Python (especially 3), inheriting&amp;nbsp;from &lt;code&gt;object&lt;/code&gt; and getting dict-based storage is the obvious thing to do; the ability to use another type isn&amp;#8217;t quite so obvious, and doing it &amp;#8220;right&amp;#8221; involves a tiny bit of extra&amp;nbsp;work.&lt;/p&gt;
&lt;p&gt;Or, consider that Lua &lt;em&gt;could&lt;/em&gt; have descriptors, but the extra bit of work (especially &lt;em&gt;design&lt;/em&gt; work) has been enough of an impediment that I&amp;#8217;ve never implemented them.  I don&amp;#8217;t think the object implementations I&amp;#8217;ve looked at have included them, either.  Super&amp;nbsp;weird!&lt;/p&gt;
&lt;p&gt;In that light, it&amp;#8217;s only natural that objects would be so strongly associated with the features Java and C++ attach to them.  I think that makes it all the more important to play around!  Look at what Moose has done.  No, really, you should bear in mind my description of how Perl does stuff and flip through the &lt;a href="http://search.cpan.org/~ether/Moose-2.2009/lib/Moose.pm"&gt;Moose documentation&lt;/a&gt;.  It&amp;#8217;s amazing what they&amp;#8217;ve&amp;nbsp;built.&lt;/p&gt;</content><category term="articles"></category><category term="tech"></category><category term="plt"></category><category term="patreon"></category></entry><entry><title>JavaScript got better while I wasn’t looking</title><link href="https://eev.ee/blog/2017/10/07/javascript-got-better-while-i-wasnt-looking/" rel="alternate"></link><published>2017-10-07T10:05:00-07:00</published><updated>2017-10-07T10:05:00-07:00</updated><author><name>Eevee</name></author><id>tag:eev.ee,2017-10-07:/blog/2017/10/07/javascript-got-better-while-i-wasnt-looking/</id><summary type="html">&lt;p&gt;&lt;a href="https://www.patreon.com/user/creators?u=199476"&gt;IndustrialRobot&lt;/a&gt; has generously donated in order to inquire:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;In the last few years there seems to have been a lot of activity with adding emojis to Unicode. Has there been an equal effort to add ‘real’ languages/glyph systems/etc?&lt;/p&gt;
&lt;p&gt;And as always, if you don’t have anything to say on that topic, feel free to choose your own. :p&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Yes.&lt;/p&gt;
&lt;p&gt;I mean, each release of Unicode lists major new additions right at the top — &lt;a href="http://www.unicode.org/versions/Unicode10.0.0/#Summary"&gt;Unicode 10&lt;/a&gt;, &lt;a href="http://www.unicode.org/versions/Unicode9.0.0/#Summary"&gt;Unicode 9&lt;/a&gt;, &lt;a href="http://www.unicode.org/versions/Unicode8.0.0/#Summary"&gt;Unicode 8&lt;/a&gt;, etc.  They also keep fastidious notes, so you can also dig into how and why these new scripts came from, by reading e.g. &lt;a href="http://www.unicode.org/L2/L2015/15337-zanabazar-square.pdf"&gt;the proposal for the addition of Zanabazar Square&lt;/a&gt;.  I don’t think I have much to add here; I’m not a real linguist, I only play one on &lt;span class="caps"&gt;TV&lt;/span&gt;.&lt;/p&gt;
&lt;p&gt;So with that out of the way, here’s something completely different!&lt;/p&gt;
</summary><content type="html">&lt;p&gt;&lt;a href="https://www.patreon.com/user/creators?u=199476"&gt;IndustrialRobot&lt;/a&gt; has generously donated in order to&amp;nbsp;inquire:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;In the last few years there seems to have been a lot of activity with adding emojis to Unicode. Has there been an equal effort to add &amp;#8216;real&amp;#8217; languages/glyph&amp;nbsp;systems/etc?&lt;/p&gt;
&lt;p&gt;And as always, if you don&amp;#8217;t have anything to say on that topic, feel free to choose your own.&amp;nbsp;:p&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Yes.&lt;/p&gt;
&lt;p&gt;I mean, each release of Unicode lists major new additions right at the top — &lt;a href="http://www.unicode.org/versions/Unicode10.0.0/#Summary"&gt;Unicode 10&lt;/a&gt;, &lt;a href="http://www.unicode.org/versions/Unicode9.0.0/#Summary"&gt;Unicode 9&lt;/a&gt;, &lt;a href="http://www.unicode.org/versions/Unicode8.0.0/#Summary"&gt;Unicode 8&lt;/a&gt;, etc.  They also keep fastidious notes, so you can also dig into how and why these new scripts came from, by reading e.g. &lt;a href="http://www.unicode.org/L2/L2015/15337-zanabazar-square.pdf"&gt;the proposal for the addition of Zanabazar Square&lt;/a&gt;.  I don&amp;#8217;t think I have much to add here; I&amp;#8217;m not a real linguist, I only play one on &lt;span class="caps"&gt;TV&lt;/span&gt;.&lt;/p&gt;
&lt;p&gt;So with that out of the way, here&amp;#8217;s something completely&amp;nbsp;different!&lt;/p&gt;


&lt;h2 id="a-brief-history-of-javascript"&gt;&lt;a class="toclink" href="#a-brief-history-of-javascript"&gt;A brief history of JavaScript&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;JavaScript was created in seven days, about eight thousand years ago.  It was pretty rough, and it stayed rough for most of its life.  But that was fine, because no one used it for anything besides having a trail of sparkles follow your mouse on their Xanga&amp;nbsp;profile.&lt;/p&gt;
&lt;p&gt;Then people discovered you could actually do a handful of useful things with JavaScript, and it saw a sharp uptick in usage.  Alas, it stayed pretty rough.  So we came up with polyfills and jQuerys and all kinds of miscellaneous things that tried to smooth over the rough parts, to varying degrees of&amp;nbsp;success.&lt;/p&gt;
&lt;p&gt;And&amp;#8230;  that&amp;#8217;s it.  That&amp;#8217;s pretty much how things stayed for a&amp;nbsp;while.&lt;/p&gt;
&lt;hr /&gt;
&lt;p&gt;I have complicated feelings about JavaScript.  I don&amp;#8217;t &lt;em&gt;hate&lt;/em&gt; it…  but I certainly don&amp;#8217;t &lt;em&gt;enjoy&lt;/em&gt; it, either.  It has some pretty neat ideas, like prototypical inheritance and &amp;#8220;everything is a value&amp;#8221;, but it buries them under a pile of annoying quirks and a woefully inadequate standard library.  The &lt;span class="caps"&gt;DOM&lt;/span&gt; APIs don&amp;#8217;t make things much better — they seem to be designed as though the target language were Java, rarely taking advantage of any interesting JavaScript features.  And the places where the APIs overlap with the language are a hilarious mess: I have to check documentation &lt;em&gt;every single time&lt;/em&gt; I use any &lt;span class="caps"&gt;API&lt;/span&gt; that returns a set of things, because there are at least three totally different conventions for handling that and I can&amp;#8217;t keep them&amp;nbsp;straight.&lt;/p&gt;
&lt;p&gt;The funny thing is that I&amp;#8217;ve been fairly happy to work with Lua, even though it shares most of the same obvious quirks as JavaScript.  Both languages are weakly typed; both treat nonexistent variables and keys as simply false values, rather than errors; both have a single data structure that doubles as both a list and a map; both use 64-bit floating-point as their only numeric type (though Lua added integers very recently); both lack a standard object model; both have very tiny standard libraries.  Hell, Lua doesn&amp;#8217;t even have exceptions, not really — you have to fake them in much the same style as&amp;nbsp;Perl.&lt;/p&gt;
&lt;p&gt;And yet none of this bothers me nearly as much in Lua.  The &lt;em&gt;differences&lt;/em&gt; between the languages are very subtle, but combined they make a huge&amp;nbsp;impact.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Lua has separate operators for addition and concatenation,&amp;nbsp;so &lt;code&gt;+&lt;/code&gt; is never ambiguous.  It also&amp;nbsp;has &lt;code&gt;printf&lt;/code&gt;-style string formatting in the standard&amp;nbsp;library.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Lua&amp;#8217;s method calls are syntactic&amp;nbsp;sugar: &lt;code&gt;foo:bar()&lt;/code&gt; just&amp;nbsp;means &lt;code&gt;foo.bar(foo)&lt;/code&gt;.  Lua doesn&amp;#8217;t even have a&amp;nbsp;special &lt;code&gt;this&lt;/code&gt; or &lt;code&gt;self&lt;/code&gt; value; the invocant just becomes the first argument.  In contrast, JavaScript invokes some hand-waved magic to set its&amp;nbsp;contextual &lt;code&gt;this&lt;/code&gt; variable, which has led to no end of&amp;nbsp;confusion.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Lua has an &lt;a href="https://eev.ee/blog/2016/11/18/iteration-in-one-language-then-all-the-others/"&gt;iteration protocol&lt;/a&gt;, as well as built-in iterators for dealing with list-style or map-style data.  JavaScript has a special&amp;nbsp;dedicated &lt;code&gt;Array&lt;/code&gt; type and clumsy built-in iteration&amp;nbsp;syntax.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Lua has operator overloading and (surprisingly flexible) module&amp;nbsp;importing.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Lua allows the keys of a map to be any value (though non-scalars are always compared by identity).  JavaScript implicitly converts keys to strings — and since there&amp;#8217;s no operator overloading, there&amp;#8217;s no way to natively fix&amp;nbsp;this.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;These are fairly minor differences, in the grand scheme of language design.  And almost every feature in Lua is implemented in a ridiculously simple way; in fact the entire language is described in complete detail in a &lt;a href="https://www.lua.org/manual/5.3/manual.html"&gt;single web page&lt;/a&gt;.  So writing JavaScript is always frustrating for me: the language is &lt;em&gt;so close&lt;/em&gt; to being much more ergonomic, and yet, it&amp;nbsp;isn&amp;#8217;t.&lt;/p&gt;
&lt;p&gt;Or, so I thought.  As it turns out, while I&amp;#8217;ve been off doing other stuff for a few years, browser vendors have been implementing all this pie-in-the-sky stuff from &amp;#8220;&lt;span class="caps"&gt;ES5&lt;/span&gt;&amp;#8221; and &amp;#8220;&lt;span class="caps"&gt;ES6&lt;/span&gt;&amp;#8221;, whatever those are.  People even upgrade their browsers now.  Lo and behold, the last time I went to write JavaScript, I found out that a number of papercuts had actually been &lt;em&gt;solved&lt;/em&gt;, and the solutions were sufficiently widely available that I could actually &lt;em&gt;use them in web code&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;The weird thing is that I &lt;em&gt;do&lt;/em&gt; hear a lot about JavaScript, but the feature I&amp;#8217;ve seen raved the most about &lt;em&gt;by far&lt;/em&gt; is probably&amp;#8230;  built-in types for working with arrays of bytes?  That&amp;#8217;s cool and all, but not exactly the most pressing concern for&amp;nbsp;me.&lt;/p&gt;
&lt;p&gt;Anyway, if you also haven&amp;#8217;t been keeping tabs on the world of JavaScript, here are some things we&amp;nbsp;missed.&lt;/p&gt;
&lt;h2 id="let"&gt;&lt;a class="toclink" href="#let"&gt;&lt;code&gt;let&lt;/code&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;&lt;em&gt;&lt;a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/let"&gt;&lt;span class="caps"&gt;MDN&lt;/span&gt; docs&lt;/a&gt; — supported in Firefox 44, Chrome 41, &lt;span class="caps"&gt;IE&lt;/span&gt; 11, Safari&amp;nbsp;10&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;I&amp;#8217;m pretty sure I first&amp;nbsp;saw &lt;code&gt;let&lt;/code&gt; over a decade ago.  Firefox has supported it for ages, but you actually had to &lt;em&gt;opt in&lt;/em&gt; by specifying JavaScript version 1.7.  Remember JavaScript versions?  You know, from back in the days when people actually &lt;a href="http://www.webreference.com/js/column26/version.html"&gt;suggested you write stuff like this&lt;/a&gt;:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="linenos"&gt;1&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;SCRIPT&lt;/span&gt; &lt;span class="na"&gt;LANGUAGE&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;JavaScript1.2&amp;quot;&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;text/javascript&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;p&gt;Yikes.&lt;/p&gt;
&lt;p&gt;Anyway,&amp;nbsp;so, &lt;code&gt;let&lt;/code&gt; declares a variable — but scoped to the immediately containing &lt;em&gt;block&lt;/em&gt;,&amp;nbsp;unlike &lt;code&gt;var&lt;/code&gt;, which scopes to the innermost &lt;em&gt;function&lt;/em&gt;.  The trouble&amp;nbsp;with &lt;code&gt;var&lt;/code&gt; was that it was very easy to make&amp;nbsp;misleading:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="linenos"&gt;1&lt;/span&gt;&lt;span class="c1"&gt;// foo exists here&lt;/span&gt;
&lt;span class="linenos"&gt;2&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="kc"&gt;true&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="linenos"&gt;3&lt;/span&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="kd"&gt;var&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;foo&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="p"&gt;...;&lt;/span&gt;
&lt;span class="linenos"&gt;4&lt;/span&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;...&lt;/span&gt;
&lt;span class="linenos"&gt;5&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="linenos"&gt;6&lt;/span&gt;&lt;span class="c1"&gt;// foo exists here too&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;If you reused the same temporary variable name in a different block, or if you expected to be shadowing an&amp;nbsp;outer &lt;code&gt;foo&lt;/code&gt;, or if you were trying to do something with creating closures in a loop, this would cause you some&amp;nbsp;trouble.&lt;/p&gt;
&lt;p&gt;But no more,&amp;nbsp;because &lt;code&gt;let&lt;/code&gt; actually scopes the way it looks like it should, the way variable declarations do in C and friends.  As an added bonus, if you refer to a variable declared&amp;nbsp;with &lt;code&gt;let&lt;/code&gt; &lt;em&gt;outside&lt;/em&gt; of where it&amp;#8217;s valid, you&amp;#8217;ll get&amp;nbsp;a &lt;code&gt;ReferenceError&lt;/code&gt; instead of a&amp;nbsp;silent &lt;code&gt;undefined&lt;/code&gt; value.&amp;nbsp;Hooray!&lt;/p&gt;
&lt;p&gt;There&amp;#8217;s one other interesting quirk&amp;nbsp;to &lt;code&gt;let&lt;/code&gt; that I can&amp;#8217;t find explicitly documented.&amp;nbsp;Consider:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="linenos"&gt;1&lt;/span&gt;&lt;span class="kd"&gt;let&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;closures&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="p"&gt;[];&lt;/span&gt;
&lt;span class="linenos"&gt;2&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="kd"&gt;let&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&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="mf"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&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="mf"&gt;4&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;i&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="linenos"&gt;3&lt;/span&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;closures&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;push&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;function&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="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&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="linenos"&gt;4&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="linenos"&gt;5&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="kd"&gt;let&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;j&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="mf"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;j&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="nx"&gt;closures&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;j&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="linenos"&gt;6&lt;/span&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;closures&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;j&lt;/span&gt;&lt;span class="p"&gt;]();&lt;/span&gt;
&lt;span class="linenos"&gt;7&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;If this code had&amp;nbsp;used &lt;code&gt;var i&lt;/code&gt;, then it would&amp;nbsp;print &lt;code&gt;4&lt;/code&gt; four times, because the&amp;nbsp;function-scoped &lt;code&gt;var i&lt;/code&gt; means each closure is sharing the&amp;nbsp;same &lt;code&gt;i&lt;/code&gt;, whose final value&amp;nbsp;is &lt;code&gt;4&lt;/code&gt;.&amp;nbsp;With &lt;code&gt;let&lt;/code&gt;, the output&amp;nbsp;is &lt;code&gt;0 1 2 3&lt;/code&gt;, as you might expect, because each run through the loop gets its&amp;nbsp;own &lt;code&gt;i&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;But wait, hang&amp;nbsp;on.&lt;/p&gt;
&lt;p&gt;The semantics of a&amp;nbsp;C-style &lt;code&gt;for&lt;/code&gt; are that the first expression is only evaluated &lt;em&gt;once&lt;/em&gt;, at the very beginning.  So there&amp;#8217;s only&amp;nbsp;one &lt;code&gt;let i&lt;/code&gt;.  In fact, it makes no sense for each run through the loop to have a&amp;nbsp;distinct &lt;code&gt;i&lt;/code&gt;, because the whole idea of the loop is to &lt;em&gt;modify&lt;/em&gt; &lt;code&gt;i&lt;/code&gt; each time&amp;nbsp;with &lt;code&gt;i++&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;I assume this is simply a special case, since it&amp;#8217;s what everyone expects.  We expect it &lt;em&gt;so&lt;/em&gt; much that I can&amp;#8217;t find anyone pointing out that the usual explanation for why it works makes no sense.  It has the interesting side effect&amp;nbsp;that &lt;code&gt;for&lt;/code&gt; no longer de-sugars to&amp;nbsp;a &lt;code&gt;while&lt;/code&gt; the same way as in C, since this will print&amp;nbsp;all &lt;code&gt;4&lt;/code&gt;s:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="linenos"&gt;1&lt;/span&gt;&lt;span class="nx"&gt;closures&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="p"&gt;[];&lt;/span&gt;
&lt;span class="linenos"&gt;2&lt;/span&gt;&lt;span class="kd"&gt;let&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&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="mf"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="linenos"&gt;3&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="nx"&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="mf"&gt;4&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="linenos"&gt;4&lt;/span&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;closures&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;push&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;function&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="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&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="linenos"&gt;5&lt;/span&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="o"&gt;++&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="linenos"&gt;6&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="linenos"&gt;7&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="kd"&gt;let&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;j&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="mf"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;j&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="nx"&gt;closures&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;j&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="linenos"&gt;8&lt;/span&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;closures&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;j&lt;/span&gt;&lt;span class="p"&gt;]();&lt;/span&gt;
&lt;span class="linenos"&gt;9&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;You&amp;#8217;d need to introduce an anonymous temporary state variable to recreate the same effect.  This isn&amp;#8217;t a problem — I&amp;#8217;m &lt;em&gt;glad&lt;/em&gt; &lt;code&gt;let&lt;/code&gt; works this way! — it just stands out to me as interesting.  Lua doesn&amp;#8217;t need a special case here, since it uses an iterator protocol that produces values rather than mutating a visible state variable, so there&amp;#8217;s no problem with having the loop variable be truly distinct on each run through the&amp;nbsp;loop.&lt;/p&gt;
&lt;h2 id="classes"&gt;&lt;a class="toclink" href="#classes"&gt;Classes&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;&lt;em&gt;&lt;a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Classes"&gt;&lt;span class="caps"&gt;MDN&lt;/span&gt; docs&lt;/a&gt; — supported in Firefox 45, Chrome 42, Safari 9, Edge&amp;nbsp;13&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;Prototypical inheritance is pretty cool.  The way JavaScript presents it is a &lt;em&gt;little bit&lt;/em&gt; opaque, unfortunately, which seems to confuse a lot of people.  JavaScript gives you enough functionality to make it work, and even makes it sound like a first-class feature with a property outright&amp;nbsp;called &lt;code&gt;prototype&lt;/code&gt;&amp;#8230;  but to actually &lt;em&gt;use&lt;/em&gt; it, you have to do a bunch of weird stuff that doesn&amp;#8217;t much look like constructing an object or&amp;nbsp;type.&lt;/p&gt;
&lt;p&gt;The funny thing is, people with almost any background get along with Python just fine, and &lt;em&gt;Python uses prototypical inheritance&lt;/em&gt;!  Nobody ever seems to notice this, because Python tucks it neatly behind&amp;nbsp;a &lt;code&gt;class&lt;/code&gt; block that works &lt;em&gt;enough&lt;/em&gt; like a Java-style class.  (Python also handles inheritance without using the prototype, so it&amp;#8217;s a little different&amp;#8230;  but I digress.  Maybe in another&amp;nbsp;post.)&lt;/p&gt;
&lt;p&gt;The point is, there&amp;#8217;s nothing fundamentally &lt;em&gt;wrong&lt;/em&gt; with how JavaScript handles objects; the ergonomics are just&amp;nbsp;terrible.&lt;/p&gt;
&lt;p&gt;Lo!  They finally added&amp;nbsp;a &lt;code&gt;class&lt;/code&gt; keyword.  Or, rather, they finally made&amp;nbsp;the &lt;code&gt;class&lt;/code&gt; keyword &lt;em&gt;do&lt;/em&gt; something; it&amp;#8217;s been reserved this entire&amp;nbsp;time.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="linenos"&gt; 1&lt;/span&gt;&lt;span class="kd"&gt;class&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;Vector&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="linenos"&gt; 2&lt;/span&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="kr"&gt;constructor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;x&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;y&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="linenos"&gt; 3&lt;/span&gt;&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;x&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="nx"&gt;x&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="linenos"&gt; 4&lt;/span&gt;&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;y&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="nx"&gt;y&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="linenos"&gt; 5&lt;/span&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="linenos"&gt; 6&lt;/span&gt;
&lt;span class="linenos"&gt; 7&lt;/span&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;get&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;magnitude&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="linenos"&gt; 8&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;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;sqrt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;x&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;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;x&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;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;y&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;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;y&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="linenos"&gt; 9&lt;/span&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="linenos"&gt;10&lt;/span&gt;
&lt;span class="linenos"&gt;11&lt;/span&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;dot&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;other&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="linenos"&gt;12&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="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;x&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="nx"&gt;other&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;x&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;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;y&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="nx"&gt;other&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;y&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="linenos"&gt;13&lt;/span&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="linenos"&gt;14&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;This is all just sugar for existing features: creating&amp;nbsp;a &lt;code&gt;Vector&lt;/code&gt; function to act as the constructor, assigning a function&amp;nbsp;to &lt;code&gt;Vector.prototype.dot&lt;/code&gt;, and whatever it is you do to make a property.  (Oh, there are properties.  I&amp;#8217;ll get to that in a&amp;nbsp;bit.)&lt;/p&gt;
&lt;p&gt;The &lt;code&gt;class&lt;/code&gt; block can be used as an expression, with or without a name.  It also supports prototypical inheritance with&amp;nbsp;an &lt;code&gt;extends&lt;/code&gt; clause and has&amp;nbsp;a &lt;code&gt;super&lt;/code&gt; pseudo-value for superclass&amp;nbsp;calls.&lt;/p&gt;
&lt;p&gt;It&amp;#8217;s a &lt;em&gt;little&lt;/em&gt; weird that the inside of&amp;nbsp;the &lt;code&gt;class&lt;/code&gt; block has its own special syntax,&amp;nbsp;with &lt;code&gt;function&lt;/code&gt; omitted and whatnot, but honestly you&amp;#8217;d have a hard time making&amp;nbsp;a &lt;code&gt;class&lt;/code&gt; block &lt;em&gt;without&lt;/em&gt; special&amp;nbsp;syntax.&lt;/p&gt;
&lt;p&gt;One severe omission here is that you can&amp;#8217;t declare &lt;em&gt;values&lt;/em&gt; inside the block, i.e. you can&amp;#8217;t just drop&amp;nbsp;a &lt;code&gt;bar = 3;&lt;/code&gt; in there if you want all your objects to share a default attribute.  The workaround is to just&amp;nbsp;do &lt;code&gt;this.bar = 3;&lt;/code&gt; inside the constructor, but I find that unsatisfying, since it defeats half the point of using&amp;nbsp;prototypes.&lt;/p&gt;
&lt;h2 id="properties"&gt;&lt;a class="toclink" href="#properties"&gt;Properties&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;&lt;em&gt;&lt;a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/defineProperty"&gt;&lt;span class="caps"&gt;MDN&lt;/span&gt; docs&lt;/a&gt; — supported in Firefox 4, Chrome 5, &lt;span class="caps"&gt;IE&lt;/span&gt; 9, Safari&amp;nbsp;5.1&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;JavaScript historically didn&amp;#8217;t have a way to intercept attribute access, which is a &lt;em&gt;travesty&lt;/em&gt;.  And by &amp;#8220;intercept attribute access&amp;#8221;, I mean that you couldn&amp;#8217;t design a&amp;nbsp;value &lt;code&gt;foo&lt;/code&gt; such that&amp;nbsp;evaluating &lt;code&gt;foo.bar&lt;/code&gt; runs some code you&amp;nbsp;wrote.&lt;/p&gt;
&lt;p&gt;Exciting news: now it does.  Or, rather, you can intercept &lt;em&gt;specific&lt;/em&gt; attributes, like in the class example above.  The&amp;nbsp;above &lt;code&gt;magnitude&lt;/code&gt; definition is equivalent&amp;nbsp;to:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="linenos"&gt;1&lt;/span&gt;&lt;span class="nb"&gt;Object&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;defineProperty&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;Vector&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;prototype&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;magnitude&amp;#39;&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="linenos"&gt;2&lt;/span&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;configurable&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="linenos"&gt;3&lt;/span&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;enumerable&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="linenos"&gt;4&lt;/span&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;get&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kd"&gt;function&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="linenos"&gt;5&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;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;sqrt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;x&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;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;x&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;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;y&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;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;y&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="linenos"&gt;6&lt;/span&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="linenos"&gt;7&lt;/span&gt;&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Beautiful.&lt;/p&gt;
&lt;p&gt;And what even are&amp;nbsp;these &lt;code&gt;configurable&lt;/code&gt; and &lt;code&gt;enumerable&lt;/code&gt; things?  It seems that &lt;em&gt;every single key on every single object&lt;/em&gt; now has its own set of three Boolean&amp;nbsp;twiddles:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;configurable&lt;/code&gt; means the property itself can be reconfigured with another call&amp;nbsp;to &lt;code&gt;Object.defineProperty&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;enumerable&lt;/code&gt; means the property appears&amp;nbsp;in &lt;code&gt;for..in&lt;/code&gt; or &lt;code&gt;Object.keys()&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;writable&lt;/code&gt; means the property value can be changed, which only applies to properties with real values rather than accessor&amp;nbsp;functions.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The incredibly wild thing is that for properties defined&amp;nbsp;by &lt;code&gt;Object.defineProperty&lt;/code&gt;, &lt;code&gt;configurable&lt;/code&gt; and &lt;code&gt;enumerable&lt;/code&gt; default to &lt;em&gt;&lt;code&gt;false&lt;/code&gt;&lt;/em&gt;, meaning that by default accessor properties are immutable &lt;em&gt;and invisible&lt;/em&gt;.  Super&amp;nbsp;weird.&lt;/p&gt;
&lt;p&gt;Nice to have, though.  And luckily, it turns out the same syntax as&amp;nbsp;in &lt;code&gt;class&lt;/code&gt; also works in object&amp;nbsp;literals.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="linenos"&gt;1&lt;/span&gt;&lt;span class="nx"&gt;Vector&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;prototype&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="p"&gt;{&lt;/span&gt;
&lt;span class="linenos"&gt;2&lt;/span&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;get&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;magnitude&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="linenos"&gt;3&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;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;sqrt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;x&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;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;x&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;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;y&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;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;y&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="linenos"&gt;4&lt;/span&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="linenos"&gt;5&lt;/span&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;...&lt;/span&gt;
&lt;span class="linenos"&gt;6&lt;/span&gt;&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;In fact, the syntax&amp;nbsp;in &lt;code&gt;class&lt;/code&gt; works in &lt;em&gt;all&lt;/em&gt; object literals, by which I mean you can define functions like&amp;nbsp;so:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="linenos"&gt;1&lt;/span&gt;&lt;span class="nx"&gt;Vector&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;prototype&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="p"&gt;{&lt;/span&gt;
&lt;span class="linenos"&gt;2&lt;/span&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;dot&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;other&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="linenos"&gt;3&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="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;x&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="nx"&gt;other&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;x&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;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;y&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="nx"&gt;other&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;y&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="linenos"&gt;4&lt;/span&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="linenos"&gt;5&lt;/span&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;...&lt;/span&gt;
&lt;span class="linenos"&gt;6&lt;/span&gt;&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Alas, I&amp;#8217;m not aware of a way to intercept &lt;em&gt;arbitrary&lt;/em&gt; attribute&amp;nbsp;access.&lt;/p&gt;
&lt;p&gt;Another feature along the same lines is &lt;a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/seal"&gt;&lt;code&gt;Object.seal()&lt;/code&gt;&lt;/a&gt;, which marks all of an object&amp;#8217;s properties as non-configurable &lt;em&gt;and&lt;/em&gt; prevents any new properties from being added to the object.  The object is still mutable, but its &amp;#8220;shape&amp;#8221; can&amp;#8217;t be changed.  And of course you can just make the object completely immutable if you want, via setting all its properties non-writable, or just using &lt;a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/freeze"&gt;&lt;code&gt;Object.freeze()&lt;/code&gt;&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;I have mixed feelings about the ability to irrevocably change something about a dynamic runtime.  It would certainly solve some gripes of former Haskell-minded colleagues, and I don&amp;#8217;t have any compelling argument against it, but it feels like it violates some unwritten contract about dynamic languages — surely any structural change made by user code should also be able to be &lt;em&gt;undone&lt;/em&gt; by user&amp;nbsp;code?&lt;/p&gt;
&lt;h2 id="slurpy-arguments"&gt;&lt;a class="toclink" href="#slurpy-arguments"&gt;Slurpy arguments&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;&lt;em&gt;&lt;a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions/rest_parameters"&gt;&lt;span class="caps"&gt;MDN&lt;/span&gt; docs&lt;/a&gt; — supported in Firefox 15, Chrome 47, Edge 12, Safari&amp;nbsp;10&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;Officially this feature is called &amp;#8220;rest parameters&amp;#8221;, but that&amp;#8217;s a terrible name, no one cares about &amp;#8220;arguments&amp;#8221; vs &amp;#8220;parameters&amp;#8221;, and &amp;#8220;slurpy&amp;#8221; is a good word.  Bless you,&amp;nbsp;Perl.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="linenos"&gt;1&lt;/span&gt;&lt;span class="kd"&gt;function&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;foo&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;b&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="nx"&gt;args&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="linenos"&gt;2&lt;/span&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="c1"&gt;// ...&lt;/span&gt;
&lt;span class="linenos"&gt;3&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Now you can&amp;nbsp;call &lt;code&gt;foo&lt;/code&gt; with as many arguments as you want, and every argument after the second will be collected&amp;nbsp;in &lt;code&gt;args&lt;/code&gt; as a regular&amp;nbsp;array.&lt;/p&gt;
&lt;p&gt;You can also do the reverse with the &lt;a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Spread_operator"&gt;spread operator&lt;/a&gt;:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="linenos"&gt;1&lt;/span&gt;&lt;span class="kd"&gt;let&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;args&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="p"&gt;[];&lt;/span&gt;
&lt;span class="linenos"&gt;2&lt;/span&gt;&lt;span class="nx"&gt;args&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;push&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mf"&gt;1&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="linenos"&gt;3&lt;/span&gt;&lt;span class="nx"&gt;args&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;push&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mf"&gt;2&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="linenos"&gt;4&lt;/span&gt;&lt;span class="nx"&gt;args&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;push&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mf"&gt;3&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="linenos"&gt;5&lt;/span&gt;&lt;span class="nx"&gt;foo&lt;/span&gt;&lt;span class="p"&gt;(...&lt;/span&gt;&lt;span class="nx"&gt;args&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;It even works in array literals, even multiple&amp;nbsp;times:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="linenos"&gt;1&lt;/span&gt;&lt;span class="kd"&gt;let&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;args2&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="p"&gt;[...&lt;/span&gt;&lt;span class="nx"&gt;args&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="nx"&gt;args&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;
&lt;span class="linenos"&gt;2&lt;/span&gt;&lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;args2&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="c1"&gt;// [1, 2, 3, 1, 2, 3]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Apparently there&amp;#8217;s also a proposal for allowing the same thing with objects inside object&amp;nbsp;literals.&lt;/p&gt;
&lt;h2 id="default-arguments"&gt;&lt;a class="toclink" href="#default-arguments"&gt;Default arguments&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;&lt;em&gt;&lt;a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions/Default_parameters"&gt;&lt;span class="caps"&gt;MDN&lt;/span&gt; docs&lt;/a&gt; — supported in Firefox 15, Chrome 49, Edge 14, Safari&amp;nbsp;10&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;Yes, arguments can have defaults now.  It&amp;#8217;s more like Sass than Python — default expressions are evaluated once &lt;em&gt;per call&lt;/em&gt;, and later default expressions can refer to earlier arguments.  I don&amp;#8217;t know how I feel about that but&amp;nbsp;whatever.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="linenos"&gt;1&lt;/span&gt;&lt;span class="kd"&gt;function&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;foo&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;n&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="mf"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;m&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="nx"&gt;n&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="mf"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;list&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="p"&gt;[])&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="linenos"&gt;2&lt;/span&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;...&lt;/span&gt;
&lt;span class="linenos"&gt;3&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Also, unlike Python, you can have an argument with a default and follow it with an argument &lt;em&gt;without&lt;/em&gt; a default, since the default default (!) is and always has been defined&amp;nbsp;as &lt;code&gt;undefined&lt;/code&gt;.  Er, let me just write it&amp;nbsp;out.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="linenos"&gt;1&lt;/span&gt;&lt;span class="kd"&gt;function&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;bar&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&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="mf"&gt;5&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;b&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="linenos"&gt;2&lt;/span&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;...&lt;/span&gt;
&lt;span class="linenos"&gt;3&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;h2 id="arrow-functions"&gt;&lt;a class="toclink" href="#arrow-functions"&gt;Arrow functions&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;&lt;em&gt;&lt;a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions/Arrow_functions"&gt;&lt;span class="caps"&gt;MDN&lt;/span&gt; docs&lt;/a&gt; — supported in Firefox 22, Chrome 45, Edge 12, Safari&amp;nbsp;10&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;Perhaps the most humble improvement is the arrow function.  It&amp;#8217;s a slightly shorter way to write an anonymous&amp;nbsp;function.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="linenos"&gt;1&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;b&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;c&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&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="p"&gt;...&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="linenos"&gt;2&lt;/span&gt;&lt;span class="nx"&gt;a&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&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="p"&gt;...&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="linenos"&gt;3&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&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="p"&gt;...&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;An arrow function &lt;em&gt;does not&lt;/em&gt;&amp;nbsp;set &lt;code&gt;this&lt;/code&gt; or some other magical values, so you can safely use an arrow function as a quick closure inside a method without having to&amp;nbsp;rebind &lt;code&gt;this&lt;/code&gt;.&amp;nbsp;Hooray!&lt;/p&gt;
&lt;p&gt;Otherwise, arrow functions act pretty much like regular functions; you can even use all the features of regular function&amp;nbsp;signatures.&lt;/p&gt;
&lt;p&gt;Arrow functions are particularly nice in combination with all the combinator-style array functions that were added a while ago, like &lt;a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/forEach"&gt;&lt;code&gt;Array.forEach&lt;/code&gt;&lt;/a&gt;.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="linenos"&gt;1&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mf"&gt;7&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;8&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;9&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nx"&gt;forEach&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&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="linenos"&gt;2&lt;/span&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="linenos"&gt;3&lt;/span&gt;&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;h2 id="symbol"&gt;&lt;a class="toclink" href="#symbol"&gt;Symbol&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;&lt;em&gt;&lt;a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Symbol"&gt;&lt;span class="caps"&gt;MDN&lt;/span&gt; docs&lt;/a&gt; — supported in Firefox 36, Chrome 38, Edge 12, Safari&amp;nbsp;9&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;This isn&amp;#8217;t quite what I&amp;#8217;d call an exciting feature, but it&amp;#8217;s necessary for explaining the next one.  It&amp;#8217;s actually&amp;#8230;  extremely&amp;nbsp;weird.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;symbol&lt;/code&gt; is a new kind of &lt;em&gt;primitive&lt;/em&gt;&amp;nbsp;(like &lt;code&gt;number&lt;/code&gt; and &lt;code&gt;string&lt;/code&gt;), not an object (like,&amp;nbsp;er, &lt;code&gt;Number&lt;/code&gt; and &lt;code&gt;String&lt;/code&gt;).  A symbol is created&amp;nbsp;with &lt;code&gt;Symbol('foo')&lt;/code&gt;.  No,&amp;nbsp;not &lt;code&gt;new Symbol('foo')&lt;/code&gt;; that throws&amp;nbsp;a &lt;code&gt;TypeError&lt;/code&gt;, for, uh, some&amp;nbsp;reason.&lt;/p&gt;
&lt;p&gt;The only point of a symbol is as a unique &lt;em&gt;key&lt;/em&gt;.  You see, symbols have one very special property: they can be used as object keys, and &lt;em&gt;will not&lt;/em&gt; be stringified.  Remember, only strings can be keys in JavaScript — even the indices of an array are, semantically speaking, still strings.  Symbols are a new exception to this&amp;nbsp;rule.&lt;/p&gt;
&lt;p&gt;Also, like other objects, two symbols don&amp;#8217;t compare equal to each&amp;nbsp;other: &lt;code&gt;Symbol('foo') != Symbol('foo')&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;The result is that symbols solve one of the problems that plagues most object systems, something I&amp;#8217;ve talked about before: &lt;em&gt;interfaces&lt;/em&gt;.  Since an interface might be implemented by any arbitrary type, and any arbitrary type might want to implement any number of arbitrary interfaces, all the method names on an interface are effectively part of a single &lt;strong&gt;global&lt;/strong&gt;&amp;nbsp;namespace.&lt;/p&gt;
&lt;p&gt;I think I need to take a moment to justify that.  If you&amp;nbsp;have &lt;code&gt;IFoo&lt;/code&gt; and &lt;code&gt;IBar&lt;/code&gt;, both with a method&amp;nbsp;called &lt;code&gt;method&lt;/code&gt;, and you want to implement both on the same type&amp;#8230;  you have a problem.  Because most object systems consider &amp;#8220;interface&amp;#8221; to mean &amp;#8220;I have a method&amp;nbsp;called &lt;code&gt;method&lt;/code&gt;&lt;span class="dquo"&gt;&amp;#8220;&lt;/span&gt;, with no way to say &lt;em&gt;which&amp;nbsp;interface&amp;#8217;s&lt;/em&gt; &lt;code&gt;method&lt;/code&gt; you mean.  This is a hard problem to avoid,&amp;nbsp;because &lt;code&gt;IFoo&lt;/code&gt; and &lt;code&gt;IBar&lt;/code&gt; might not even come from the same library.  Occasionally languages offer a clumsy way to &amp;#8220;rename&amp;#8221; one method or the other, but the most common approach seems to be for interface designers to avoid names that sound &amp;#8220;too common&amp;#8221;.  You end up with redundant mouthfuls&amp;nbsp;like &lt;code&gt;IFoo.foo_method&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;This incredibly sucks, and I&amp;#8217;m only aware of a few languages that avoid the problem: C#, Swift, and the &lt;span class="caps"&gt;ML&lt;/span&gt; family and Rust.  I group the latter together because I&amp;#8217;m pretty sure Rust borrowed the &lt;span class="caps"&gt;ML&lt;/span&gt; approach&amp;nbsp;wholesale.&lt;/p&gt;
&lt;p&gt;In Rust, you define all the methods for a particular trait (interface) in a &lt;em&gt;separate block&lt;/em&gt;, away from the type&amp;#8217;s &amp;#8220;own&amp;#8221; methods.  It&amp;#8217;s pretty slick.  You can still&amp;nbsp;do &lt;code&gt;obj.method()&lt;/code&gt;, and as long as there&amp;#8217;s only&amp;nbsp;one &lt;code&gt;method&lt;/code&gt; among all the available traits, you&amp;#8217;ll get that one.  If not, there&amp;#8217;s &lt;a href="https://doc.rust-lang.org/book/first-edition/ufcs.html"&gt;syntax&lt;/a&gt; for explicitly saying which trait you mean, which I can never remember because I&amp;#8217;ve never had to use it.  Also, because the methods explicitly belong to the &lt;em&gt;trait&lt;/em&gt; and aren&amp;#8217;t merely hung on the &lt;em&gt;object&lt;/em&gt;, Rust only looks for method names in traits &lt;em&gt;that have been imported into the current file&lt;/em&gt;, which drastically cuts down on potential ambiguity (and prevents existing code from breaking when a type implements a new&amp;nbsp;trait!).&lt;/p&gt;
&lt;p&gt;Symbols are JavaScript&amp;#8217;s answer to this problem.  If you want to define some interface, you can name its methods with &lt;em&gt;symbols&lt;/em&gt;, which are guaranteed to be unique.  You just have to make sure you keep the symbol around somewhere accessible so other people can actually use it.  (Or&amp;#8230;&amp;nbsp;not?)&lt;/p&gt;
&lt;p&gt;The interesting thing is that JavaScript now has several of its own symbols built in, allowing user objects to implement features that were previously reserved for built-in types.  For example, you can use&amp;nbsp;the &lt;code&gt;Symbol.hasInstance&lt;/code&gt; symbol — which is simply where the language is storing an existing symbol and is &lt;em&gt;not&lt;/em&gt; the same&amp;nbsp;as &lt;code&gt;Symbol('hasInstance')&lt;/code&gt;! — to&amp;nbsp;override &lt;code&gt;instanceof&lt;/code&gt;:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="linenos"&gt;1&lt;/span&gt;&lt;span class="c1"&gt;// oh my god don&amp;#39;t do this though&lt;/span&gt;
&lt;span class="linenos"&gt;2&lt;/span&gt;&lt;span class="kd"&gt;class&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;EvenNumber&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="linenos"&gt;3&lt;/span&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;static&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;Symbol&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;hasInstance&lt;/span&gt;&lt;span class="p"&gt;](&lt;/span&gt;&lt;span class="nx"&gt;obj&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="linenos"&gt;4&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="nx"&gt;obj&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="mf"&gt;2&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="mf"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="linenos"&gt;5&lt;/span&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="linenos"&gt;6&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="linenos"&gt;7&lt;/span&gt;&lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mf"&gt;2&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ow"&gt;instanceof&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;EvenNumber&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="c1"&gt;// true&lt;/span&gt;
&lt;span class="linenos"&gt;8&lt;/span&gt;&lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mf"&gt;3&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ow"&gt;instanceof&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;EvenNumber&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="c1"&gt;// false&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Oh, and those brackets&amp;nbsp;around &lt;code&gt;Symbol.hasInstance&lt;/code&gt; are a sort of reverse-quoting — they indicate an expression to use where the language would normally expect a literal identifier.  I think they work as object keys, too, and maybe some other&amp;nbsp;places.&lt;/p&gt;
&lt;p&gt;The equivalent in Python is to implement a method&amp;nbsp;called &lt;code&gt;__instancecheck__&lt;/code&gt;, a name which is not special in any way except that Python has reserved all method names of the&amp;nbsp;form &lt;code&gt;__foo__&lt;/code&gt;.  That&amp;#8217;s great for Python, but doesn&amp;#8217;t really help user code.  JavaScript has actually outclassed (ho ho) Python&amp;nbsp;here.&lt;/p&gt;
&lt;p&gt;Of&amp;nbsp;course, &lt;code&gt;obj[BobNamespace.some_method]()&lt;/code&gt; is not the prettiest way to call an interface method, so it&amp;#8217;s not &lt;em&gt;perfect&lt;/em&gt;.  I imagine this would be best implemented in user code by exposing a polymorphic function, similar to how&amp;nbsp;Python&amp;#8217;s &lt;code&gt;len(obj)&lt;/code&gt; pretty much just&amp;nbsp;calls &lt;code&gt;obj.__len__()&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;I only bring this up because it&amp;#8217;s the plumbing behind one of the most incredible things in JavaScript that I didn&amp;#8217;t even know about until I started writing this post.  I&amp;#8217;m so excited oh my gosh.  Are you ready?&amp;nbsp;It&amp;#8217;s:&lt;/p&gt;
&lt;h2 id="iteration-protocol"&gt;&lt;a class="toclink" href="#iteration-protocol"&gt;Iteration protocol&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;&lt;em&gt;&lt;a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Iteration_protocols"&gt;&lt;span class="caps"&gt;MDN&lt;/span&gt; docs&lt;/a&gt; — supported in Firefox 27, Chrome 39, Safari 10; still experimental in&amp;nbsp;Edge&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;Yes!  Amazing!  JavaScript has first-class support for iteration!  I can&amp;#8217;t even believe&amp;nbsp;this.&lt;/p&gt;
&lt;p&gt;It works pretty much &lt;a href="https://eev.ee/blog/2016/11/18/iteration-in-one-language-then-all-the-others/"&gt;how you&amp;#8217;d expect&lt;/a&gt;, or at least, how &lt;em&gt;I&amp;#8217;d&lt;/em&gt; expect.  You give your object a method&amp;nbsp;called &lt;code&gt;Symbol.iterator&lt;/code&gt;, and that returns an&amp;nbsp;iterator.&lt;/p&gt;
&lt;p&gt;What&amp;#8217;s an iterator?  It&amp;#8217;s an object with&amp;nbsp;a &lt;code&gt;next()&lt;/code&gt; method that returns the next value and whether the iterator is&amp;nbsp;exhausted.&lt;/p&gt;
&lt;p&gt;Wait, wait, wait a second.  Hang on.  The method is&amp;nbsp;called &lt;code&gt;next&lt;/code&gt;?  Really?  You didn&amp;#8217;t go&amp;nbsp;for &lt;code&gt;Symbol.next&lt;/code&gt;?  Python 2 did exactly the same thing, then realized its mistake and changed it&amp;nbsp;to &lt;code&gt;__next__&lt;/code&gt; in Python 3.  Why did you do&amp;nbsp;this?&lt;/p&gt;
&lt;p&gt;Well, anyway.  My go-to test of an iterator protocol is how hard it is to write an equivalent to&amp;nbsp;Python&amp;#8217;s &lt;code&gt;enumerate()&lt;/code&gt;, which takes a list and iterates over its values &lt;em&gt;and&lt;/em&gt; their indices.  In Python it looks like&amp;nbsp;this:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="linenos"&gt;1&lt;/span&gt;&lt;span class="k"&gt;for&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;value&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nb"&gt;enumerate&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;one&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;two&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;three&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;]):&lt;/span&gt;
&lt;span class="linenos"&gt;2&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;i&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="linenos"&gt;3&lt;/span&gt;&lt;span class="c1"&gt;# 0 one&lt;/span&gt;
&lt;span class="linenos"&gt;4&lt;/span&gt;&lt;span class="c1"&gt;# 1 two&lt;/span&gt;
&lt;span class="linenos"&gt;5&lt;/span&gt;&lt;span class="c1"&gt;# 2 three&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;It&amp;#8217;s super nice to have, and I&amp;#8217;m always amazed when languages with &amp;#8220;strong&amp;#8221; &amp;#8220;support&amp;#8221; for iteration don&amp;#8217;t have it.  Like, C# doesn&amp;#8217;t.  So if you want to iterate over a list but also need indices, you need to fall back to a&amp;nbsp;C-style &lt;code&gt;for&lt;/code&gt; loop.  And if you want to iterate over a &lt;em&gt;lazy&lt;/em&gt; or &lt;em&gt;arbitrary&lt;/em&gt; iterable but also need indices, you need to track it yourself with a counter.&amp;nbsp;Ridiculous.&lt;/p&gt;
&lt;p&gt;Here&amp;#8217;s my attempt at building it in&amp;nbsp;JavaScript.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="linenos"&gt; 1&lt;/span&gt;&lt;span class="kd"&gt;function&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;enumerate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;iterable&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="linenos"&gt; 2&lt;/span&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="c1"&gt;// Return a new iter*able* object with a Symbol.iterator method that&lt;/span&gt;
&lt;span class="linenos"&gt; 3&lt;/span&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="c1"&gt;// returns an iterator.&lt;/span&gt;
&lt;span class="linenos"&gt; 4&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="p"&gt;{&lt;/span&gt;
&lt;span class="linenos"&gt; 5&lt;/span&gt;&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;Symbol&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;iterator&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kd"&gt;function&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="linenos"&gt; 6&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="nx"&gt;iterator&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="nx"&gt;iterable&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;Symbol&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;iterator&lt;/span&gt;&lt;span class="p"&gt;]();&lt;/span&gt;
&lt;span class="linenos"&gt; 7&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="nx"&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="mf"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="linenos"&gt; 8&lt;/span&gt;
&lt;span class="linenos"&gt; 9&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="p"&gt;{&lt;/span&gt;
&lt;span class="linenos"&gt;10&lt;/span&gt;&lt;span class="w"&gt;                &lt;/span&gt;&lt;span class="nx"&gt;next&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kd"&gt;function&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="linenos"&gt;11&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="nx"&gt;nextval&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="nx"&gt;iterator&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;next&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="linenos"&gt;12&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="o"&gt;!&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;nextval&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;done&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="linenos"&gt;13&lt;/span&gt;&lt;span class="w"&gt;                        &lt;/span&gt;&lt;span class="nx"&gt;nextval&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;value&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="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;nextval&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;
&lt;span class="linenos"&gt;14&lt;/span&gt;&lt;span class="w"&gt;                        &lt;/span&gt;&lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="o"&gt;++&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="linenos"&gt;15&lt;/span&gt;&lt;span class="w"&gt;                    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="linenos"&gt;16&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="nx"&gt;nextval&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="linenos"&gt;17&lt;/span&gt;&lt;span class="w"&gt;                &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="linenos"&gt;18&lt;/span&gt;&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="p"&gt;};&lt;/span&gt;
&lt;span class="linenos"&gt;19&lt;/span&gt;&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="linenos"&gt;20&lt;/span&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;};&lt;/span&gt;
&lt;span class="linenos"&gt;21&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="linenos"&gt;22&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="kd"&gt;let&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;of&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;enumerate&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;one&amp;#39;&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;two&amp;#39;&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;three&amp;#39;&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="linenos"&gt;23&lt;/span&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="linenos"&gt;24&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="linenos"&gt;25&lt;/span&gt;&lt;span class="c1"&gt;// 0 one&lt;/span&gt;
&lt;span class="linenos"&gt;26&lt;/span&gt;&lt;span class="c1"&gt;// 1 two&lt;/span&gt;
&lt;span class="linenos"&gt;27&lt;/span&gt;&lt;span class="c1"&gt;// 2 three&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Incidentally, &lt;code&gt;for..of&lt;/code&gt; (which iterates over a sequence,&amp;nbsp;unlike &lt;code&gt;for..in&lt;/code&gt; which iterates over keys — &lt;em&gt;obviously&lt;/em&gt;) is finally supported in Edge 12.&amp;nbsp;Hallelujah.&lt;/p&gt;
&lt;p&gt;Oh,&amp;nbsp;and &lt;code&gt;let [i, value]&lt;/code&gt; is &lt;a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Destructuring_assignment"&gt;destructuring assignment&lt;/a&gt;, which is &lt;em&gt;also&lt;/em&gt; a thing now and works with objects as well.  You can even use the splat operator with it!  Like Python!  (And you can use it in function signatures!  Like Python!  Wait, no, Python decided that was terrible and removed it in&amp;nbsp;3&amp;#8230;)&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="linenos"&gt;1&lt;/span&gt;&lt;span class="kd"&gt;let&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;x&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;y&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="nx"&gt;others&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="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;apple&amp;#39;&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;orange&amp;#39;&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;cherry&amp;#39;&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;banana&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;It&amp;#8217;s a Halloween miracle.&amp;nbsp;🎃&lt;/p&gt;
&lt;h2 id="generators"&gt;&lt;a class="toclink" href="#generators"&gt;Generators&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;&lt;em&gt;&lt;a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/function%2a"&gt;&lt;span class="caps"&gt;MDN&lt;/span&gt; docs&lt;/a&gt; — supported in Firefox 26, Chrome 39, Edge 13, Safari&amp;nbsp;10&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;That&amp;#8217;s right, JavaScript has goddamn generators now.  It&amp;#8217;s basically just copying Python and adding a lot of superfluous punctuation everywhere.  Not that I&amp;#8217;m&amp;nbsp;complaining.&lt;/p&gt;
&lt;p&gt;Also, generators are themselves iterable, so I&amp;#8217;m going to cut to the chase and rewrite&amp;nbsp;my &lt;code&gt;enumerate()&lt;/code&gt; with a&amp;nbsp;generator.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="linenos"&gt; 1&lt;/span&gt;&lt;span class="kd"&gt;function&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;enumerate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;iterable&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="linenos"&gt; 2&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="nx"&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="mf"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="linenos"&gt; 3&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="kd"&gt;let&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;of&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;iterable&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="linenos"&gt; 4&lt;/span&gt;&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="k"&gt;yield&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;
&lt;span class="linenos"&gt; 5&lt;/span&gt;&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="o"&gt;++&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="linenos"&gt; 6&lt;/span&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="linenos"&gt; 7&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="linenos"&gt; 8&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="kd"&gt;let&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;of&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;enumerate&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;one&amp;#39;&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;two&amp;#39;&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;three&amp;#39;&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="linenos"&gt; 9&lt;/span&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="linenos"&gt;10&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="linenos"&gt;11&lt;/span&gt;&lt;span class="c1"&gt;// 0 one&lt;/span&gt;
&lt;span class="linenos"&gt;12&lt;/span&gt;&lt;span class="c1"&gt;// 1 two&lt;/span&gt;
&lt;span class="linenos"&gt;13&lt;/span&gt;&lt;span class="c1"&gt;// 2 three&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Amazing.  You can also use generators to&amp;nbsp;implement &lt;code&gt;Symbol.iterator&lt;/code&gt;, much like using a generator&amp;nbsp;for &lt;code&gt;__iter__&lt;/code&gt; in&amp;nbsp;Python.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;function*&lt;/code&gt; is a pretty strange choice of syntax, but whatever?  I guess it also lets them&amp;nbsp;make &lt;code&gt;yield&lt;/code&gt; only act as a keyword inside a generator, for ultimate backwards&amp;nbsp;compatibility.&lt;/p&gt;
&lt;p&gt;JavaScript generators support everything Python generators&amp;nbsp;do: &lt;code&gt;yield*&lt;/code&gt; yields every item from a subsequence, like&amp;nbsp;Python&amp;#8217;s &lt;code&gt;yield from&lt;/code&gt;; generators can return final values; you can pass values back into the generator if you iterate it by hand.  No, really, I wasn&amp;#8217;t kidding, &lt;em&gt;it&amp;#8217;s basically just copying Python&lt;/em&gt;.  It&amp;#8217;s great.  You could now&amp;nbsp;build &lt;code&gt;asyncio&lt;/code&gt; in&amp;nbsp;JavaScript!&lt;/p&gt;
&lt;p&gt;In fact, &lt;a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/async_function"&gt;they did that&lt;/a&gt;!  JavaScript now&amp;nbsp;has &lt;code&gt;async&lt;/code&gt; and &lt;code&gt;await&lt;/code&gt;.&amp;nbsp;An &lt;code&gt;async function&lt;/code&gt; returns a &lt;a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise"&gt;&lt;code&gt;Promise&lt;/code&gt;&lt;/a&gt;, which is also a built-in type now.&amp;nbsp;Amazing.&lt;/p&gt;
&lt;h2 id="sets-and-maps"&gt;&lt;a class="toclink" href="#sets-and-maps"&gt;Sets and maps&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;&lt;em&gt;&lt;a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map"&gt;&lt;span class="caps"&gt;MDN&lt;/span&gt; docs for Map&lt;/a&gt; — &lt;a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Set"&gt;&lt;span class="caps"&gt;MDN&lt;/span&gt; docs for Set&lt;/a&gt; — supported in Firefox 13, Chrome 38, &lt;span class="caps"&gt;IE&lt;/span&gt; 11, Safari&amp;nbsp;7.1&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;I did &lt;em&gt;not&lt;/em&gt; save the best for last.  This is much less exciting than generators.  But still&amp;nbsp;exciting.&lt;/p&gt;
&lt;p&gt;The only data structure in JavaScript is the object, a map where the keys are strings.  (Or now, also symbols, I guess.)  That means you can&amp;#8217;t readily use custom values as keys, nor simulate a set of arbitrary objects.  And you have to worry about people mucking&amp;nbsp;with &lt;code&gt;Object.prototype&lt;/code&gt;,&amp;nbsp;yikes.&lt;/p&gt;
&lt;p&gt;But now,&amp;nbsp;there&amp;#8217;s &lt;code&gt;Map&lt;/code&gt; and &lt;code&gt;Set&lt;/code&gt;!&amp;nbsp;Wow.&lt;/p&gt;
&lt;p&gt;Unfortunately, because&amp;nbsp;JavaScript, &lt;code&gt;Map&lt;/code&gt; couldn&amp;#8217;t use the indexing operators without losing the ability to have methods, so you have to use a boring old method-based &lt;span class="caps"&gt;API&lt;/span&gt;.&amp;nbsp;But &lt;code&gt;Map&lt;/code&gt; has convenient methods that plain objects don&amp;#8217;t,&amp;nbsp;like &lt;code&gt;entries()&lt;/code&gt; to iterate over pairs of keys and values.  In fact, you can use a map&amp;nbsp;with &lt;code&gt;for..of&lt;/code&gt; to get key/value pairs.  So that&amp;#8217;s&amp;nbsp;nice.&lt;/p&gt;
&lt;p&gt;Perhaps more interesting, there&amp;#8217;s also now a &lt;a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/WeakMap"&gt;&lt;code&gt;WeakMap&lt;/code&gt;&lt;/a&gt; and &lt;a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/WeakSet"&gt;&lt;code&gt;WeakSet&lt;/code&gt;&lt;/a&gt;, where the keys are weak references.  I don&amp;#8217;t think JavaScript had any way to do weak references before this, so that&amp;#8217;s pretty slick.  There&amp;#8217;s no obvious way to hold a weak &lt;em&gt;value&lt;/em&gt;, but I guess you could substitute&amp;nbsp;a &lt;code&gt;WeakSet&lt;/code&gt; with only one&amp;nbsp;item.&lt;/p&gt;
&lt;h2 id="template-literals"&gt;&lt;a class="toclink" href="#template-literals"&gt;Template literals&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;&lt;em&gt;&lt;a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Template_literals"&gt;&lt;span class="caps"&gt;MDN&lt;/span&gt; docs&lt;/a&gt; — supported in Firefox 34, Chrome 41, Edge 12, Safari&amp;nbsp;9&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;Template literals are JavaScript&amp;#8217;s answer to string interpolation, which has historically been a huge pain in the ass because it doesn&amp;#8217;t even have string formatting in the standard&amp;nbsp;library.&lt;/p&gt;
&lt;p&gt;They&amp;#8217;re just strings delimited by backticks instead of quotes.  They can span multiple lines and contain&amp;nbsp;expressions.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="linenos"&gt;1&lt;/span&gt;&lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sb"&gt;`one plus&lt;/span&gt;
&lt;span class="linenos"&gt;2&lt;/span&gt;&lt;span class="sb"&gt;two is &lt;/span&gt;&lt;span class="si"&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;+&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;2&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sb"&gt;`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Someone decided it would be a good idea to allow nesting more sets of backticks inside&amp;nbsp;a &lt;code&gt;${}&lt;/code&gt; expression, so, good luck to syntax&amp;nbsp;highlighters.&lt;/p&gt;
&lt;p&gt;However, someone also had the most incredible idea ever, which was to add syntax allowing &lt;em&gt;user code&lt;/em&gt; to do the interpolation — so you can do custom escaping, when absolutely necessary, which is virtually never, because &amp;#8220;escaping&amp;#8221; means you&amp;#8217;re building a structured format by slopping strings together willy-nilly instead of using some &lt;span class="caps"&gt;API&lt;/span&gt; that works with the&amp;nbsp;structure.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="linenos"&gt; 1&lt;/span&gt;&lt;span class="c1"&gt;// OF COURSE, YOU SHOULDN&amp;#39;T BE DOING THIS ANYWAY; YOU SHOULD BUILD HTML WITH&lt;/span&gt;
&lt;span class="linenos"&gt; 2&lt;/span&gt;&lt;span class="c1"&gt;// THE DOM API AND USE .textContent FOR LITERAL TEXT.  BUT AS AN EXAMPLE:&lt;/span&gt;
&lt;span class="linenos"&gt; 3&lt;/span&gt;&lt;span class="kd"&gt;function&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;html&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;literals&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="nx"&gt;values&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="linenos"&gt; 4&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="nx"&gt;ret&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="p"&gt;[];&lt;/span&gt;
&lt;span class="linenos"&gt; 5&lt;/span&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;literals&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;forEach&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;literal&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&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;=&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="linenos"&gt; 6&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="nx"&gt;i&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="mf"&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="linenos"&gt; 7&lt;/span&gt;&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="c1"&gt;// Is there seriously still not a built-in function for doing this?&lt;/span&gt;
&lt;span class="linenos"&gt; 8&lt;/span&gt;&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="c1"&gt;// Well, probably because you SHOULDN&amp;#39;T BE DOING IT&lt;/span&gt;
&lt;span class="linenos"&gt; 9&lt;/span&gt;&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="nx"&gt;ret&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;push&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;values&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&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="mf"&gt;1&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="linenos"&gt;10&lt;/span&gt;&lt;span class="w"&gt;                &lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;replace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sr"&gt;/&amp;amp;/g&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;amp;amp;&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="linenos"&gt;11&lt;/span&gt;&lt;span class="w"&gt;                &lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;replace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sr"&gt;/&amp;lt;/g&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;amp;lt;&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="linenos"&gt;12&lt;/span&gt;&lt;span class="w"&gt;                &lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;replace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sr"&gt;/&amp;gt;/g&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;amp;gt;&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="linenos"&gt;13&lt;/span&gt;&lt;span class="w"&gt;                &lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;replace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sr"&gt;/&amp;quot;/g&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;amp;quot;&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="linenos"&gt;14&lt;/span&gt;&lt;span class="w"&gt;                &lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;replace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sr"&gt;/&amp;#39;/g&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;amp;apos;&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
&lt;span class="linenos"&gt;15&lt;/span&gt;&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="linenos"&gt;16&lt;/span&gt;&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nx"&gt;ret&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;push&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;literal&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="linenos"&gt;17&lt;/span&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="linenos"&gt;18&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="nx"&gt;ret&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;join&lt;/span&gt;&lt;span class="p"&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="linenos"&gt;19&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="linenos"&gt;20&lt;/span&gt;&lt;span class="kd"&gt;let&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;username&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="s1"&gt;&amp;#39;Bob&amp;lt;script&amp;gt;&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="linenos"&gt;21&lt;/span&gt;&lt;span class="kd"&gt;let&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;result&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="nx"&gt;html&lt;/span&gt;&lt;span class="sb"&gt;`&amp;lt;b&amp;gt;Hello, &lt;/span&gt;&lt;span class="si"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;username&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sb"&gt;!&amp;lt;/b&amp;gt;`&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="linenos"&gt;22&lt;/span&gt;&lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;result&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="linenos"&gt;23&lt;/span&gt;&lt;span class="c1"&gt;// &amp;lt;b&amp;gt;Hello, Bob&amp;amp;lt;script&amp;amp;gt;!&amp;lt;/b&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;It&amp;#8217;s a shame this feature is in JavaScript, the language where you are least likely to need&amp;nbsp;it.&lt;/p&gt;
&lt;h2 id="trailing-commas"&gt;&lt;a class="toclink" href="#trailing-commas"&gt;Trailing commas&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Remember how you couldn&amp;#8217;t do this for ages, because ass-old &lt;span class="caps"&gt;IE&lt;/span&gt; considered it a syntax error and would reject the entire&amp;nbsp;script?&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="linenos"&gt;1&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="linenos"&gt;2&lt;/span&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;a&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;one&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="linenos"&gt;3&lt;/span&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;b&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;two&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="linenos"&gt;4&lt;/span&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;c&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;three&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="c1"&gt;// &amp;lt;- THIS GUY RIGHT HERE&lt;/span&gt;
&lt;span class="linenos"&gt;5&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Well now it&amp;#8217;s part of the goddamn spec and if there&amp;#8217;s &lt;em&gt;anything&lt;/em&gt; in this post you can rely on, it&amp;#8217;s &lt;em&gt;this&lt;/em&gt;.  In fact you can use &lt;span class="caps"&gt;AS&lt;/span&gt; &lt;span class="caps"&gt;MANY&lt;/span&gt; &lt;span class="caps"&gt;GODDAMN&lt;/span&gt; &lt;span class="caps"&gt;TRAILING&lt;/span&gt; &lt;span class="caps"&gt;COMMAS&lt;/span&gt; &lt;span class="caps"&gt;AS&lt;/span&gt; &lt;span class="caps"&gt;YOU&lt;/span&gt; &lt;span class="caps"&gt;WANT&lt;/span&gt;.  But only in&amp;nbsp;arrays.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="linenos"&gt;1&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mf"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;3&lt;/span&gt;&lt;span class="p"&gt;,,,,,,,,,,,,,,,,,,,,,,,,,]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Apparently that has the bizarre side effect of reserving extra space at the end of the array, without putting values&amp;nbsp;there.&lt;/p&gt;
&lt;h2 id="and-more-probably"&gt;&lt;a class="toclink" href="#and-more-probably"&gt;And more, probably&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Like &lt;a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Strict_mode"&gt;strict mode&lt;/a&gt;, which makes a few silent &amp;#8220;errors&amp;#8221; be actual errors, forces you to declare variables (no implicit globals!), and forbids the completely&amp;nbsp;bozotic &lt;code&gt;with&lt;/code&gt; block.&lt;/p&gt;
&lt;p&gt;Or &lt;code&gt;String.trim()&lt;/code&gt;, which trims whitespace off of&amp;nbsp;strings.&lt;/p&gt;
&lt;p&gt;Or&amp;#8230;  &lt;code&gt;Math.sign()&lt;/code&gt;?  That&amp;#8217;s new?  Seriously?  Well,&amp;nbsp;okay.&lt;/p&gt;
&lt;p&gt;Or the &lt;a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Proxy"&gt;&lt;code&gt;Proxy&lt;/code&gt;&lt;/a&gt; type, which lets you customize indexing and assignment and calling.  Oh.  I guess that &lt;em&gt;is&lt;/em&gt; possible, though this is a pretty weird way to do it; why not just use symbol-named&amp;nbsp;methods?&lt;/p&gt;
&lt;p&gt;You can write Unicode escapes for astral plane characters in strings (or identifiers!),&amp;nbsp;as &lt;code&gt;\u{XXXXXXXX}&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;There&amp;#8217;s&amp;nbsp;a &lt;code&gt;const&lt;/code&gt; now?  I extremely don&amp;#8217;t care, just name it in all caps and don&amp;#8217;t reassign it, come&amp;nbsp;on.&lt;/p&gt;
&lt;p&gt;There&amp;#8217;s also a mountain of other minor things, which you can peruse at your leisure via &lt;span class="caps"&gt;MDN&lt;/span&gt; or the &lt;a href="http://kangax.github.io/compat-table/es6/"&gt;ECMAScript compatibility tables&lt;/a&gt; (note the links at the top,&amp;nbsp;too).&lt;/p&gt;
&lt;p&gt;That&amp;#8217;s all I&amp;#8217;ve got.  I still wouldn&amp;#8217;t say I&amp;#8217;m a &lt;em&gt;big fan&lt;/em&gt; of JavaScript, but it&amp;#8217;s definitely making an effort to clean up some goofy inconsistencies and solve common problems.  I think I could even write some without yelling on Twitter about it&amp;nbsp;now.&lt;/p&gt;
&lt;p&gt;On the other hand, if you&amp;#8217;re still stuck supporting &lt;span class="caps"&gt;IE&lt;/span&gt; 10 for some reason&amp;#8230;  well, er, my&amp;nbsp;condolences.&lt;/p&gt;</content><category term="articles"></category><category term="tech"></category><category term="patreon"></category></entry><entry><title>Nazis, are bad</title><link href="https://eev.ee/blog/2017/08/13/nazis-are-bad/" rel="alternate"></link><published>2017-08-13T19:58:00-07:00</published><updated>2017-08-13T19:58:00-07:00</updated><author><name>Eevee</name></author><id>tag:eev.ee,2017-08-13:/blog/2017/08/13/nazis-are-bad/</id><summary type="html">&lt;p&gt;Anonymous asks:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Could you talk about something related to the management/moderation and growth of online communities? &lt;span class="caps"&gt;IOW&lt;/span&gt; your thoughts on online community management, if any.&lt;/p&gt;
&lt;p&gt;I think you’ve tweeted about this stuff in the past so I suspect you have thoughts on this, but if not, again, feel free to just blog about … anything :)&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Oh, I think I have some stuff to say about community management, in light of recent events.  None of it hasn’t already been said elsewhere, and I wouldn’t say it’s really about “online” or has a strong “point”, but I have to get this out.&lt;/p&gt;
&lt;p&gt;Hopefully the content warning is implicit in the title.&lt;/p&gt;
</summary><content type="html">&lt;p&gt;Anonymous&amp;nbsp;asks:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Could you talk about something related to the management/moderation and growth of online communities? &lt;span class="caps"&gt;IOW&lt;/span&gt; your thoughts on online community management, if&amp;nbsp;any.&lt;/p&gt;
&lt;p&gt;I think you&amp;#8217;ve tweeted about this stuff in the past so I suspect you have thoughts on this, but if not, again, feel free to just blog about &amp;#8230; anything&amp;nbsp;:)&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Oh, I think I have some stuff to say about community management, in light of recent events.  None of it hasn&amp;#8217;t already been said elsewhere, and I wouldn&amp;#8217;t say it&amp;#8217;s really about &amp;#8220;online&amp;#8221; or has a strong &amp;#8220;point&amp;#8221;, but I have to get this&amp;nbsp;out.&lt;/p&gt;
&lt;p&gt;Hopefully the content warning is implicit in the&amp;nbsp;title.&lt;/p&gt;


&lt;hr /&gt;
&lt;p&gt;I am&amp;nbsp;frustrated.&lt;/p&gt;
&lt;p&gt;I&amp;#8217;ve gone on before about a &lt;a href="https://eev.ee/blog/2016/07/22/on-a-technicality/"&gt;particularly bothersome phenomenon&lt;/a&gt; that hurts a lot of small online communities: often, people are willing to tolerate the misery of others in a community, but then get up in arms when someone pushes back.  Someone makes a lot of off-hand, off-color comments about women?  Uses a lot of dog-whistle terms?  Eh, they&amp;#8217;re not bothering anyone, or at least not bothering me.  Someone else gets tired of it and tells them to knock it off?  &lt;em&gt;Whoa there!&lt;/em&gt;  Now we have the &lt;em&gt;appearance&lt;/em&gt; of conflict, which is unacceptable, and people will turn on the person who&amp;#8217;s pissed off — even though they&amp;#8217;ve been at the butt end of an &lt;em&gt;invisible&lt;/em&gt; conflict for who knows how long.  The &lt;em&gt;appearance&lt;/em&gt; of peace is paramount, even if it means a large chunk of the population is quietly&amp;nbsp;miserable.&lt;/p&gt;
&lt;p&gt;Okay, so now, imagine that on a vastly larger scale, and also those annoying people who know how to skirt the rules are&amp;nbsp;Nazis.&lt;/p&gt;
&lt;hr /&gt;
&lt;p&gt;The label &amp;#8220;Nazi&amp;#8221; gets thrown around a lot lately, probably far too easily.  But when I see a group of people doing the Hitler salute, waving large Nazi flags, wearing Nazi armbands styled after the &lt;span class="caps"&gt;SS&lt;/span&gt;, well&amp;#8230;  if the shoe fits, right?  I suppose they might have flown across the country to join a torch-bearing mob &lt;em&gt;ironically&lt;/em&gt;, but if so, the joke is going way over my head.  (Was the murder ironic, too?)  Maybe they&amp;#8217;re not Nazis in the sense that the original party doesn&amp;#8217;t exist any more, but for ease of writing, let&amp;#8217;s refer to &amp;#8220;someone who espouses Nazi ideology and deliberately bears a number of Nazi symbols&amp;#8221; as, well, &amp;#8220;a&amp;nbsp;Nazi&amp;#8221;.&lt;/p&gt;
&lt;p&gt;This isn&amp;#8217;t a new thing, either; I&amp;#8217;ve stumbled upon any number of Twitter accounts that are decorated in Nazi regalia.  I suppose the trouble arises when perfectly innocent members of the alt-right get unfairly labelled as&amp;nbsp;Nazis.&lt;/p&gt;
&lt;p&gt;But hang on; this march was called &amp;#8220;Unite the Right&amp;#8221; and was intended to bring together various far right sub-groups.  So what does their choice of aesthetic say about those sub-groups?  I haven&amp;#8217;t heard, say, alt-right coiner Richard Spencer denounce the use of Nazi symbology — extra notable since &lt;em&gt;he was fucking there&lt;/em&gt; and apparently didn&amp;#8217;t care to discourage&amp;nbsp;it.&lt;/p&gt;
&lt;hr /&gt;
&lt;p&gt;And so begins the rule-skirting.  &amp;#8220;Nazi&amp;#8221; is definitely overused, but even using it to describe white supremacists who make not-so-subtle nods to Hitler is likely to earn you some sarcastic derailment.  A Nazi?  Oh, so is &lt;em&gt;everyone&lt;/em&gt; you don&amp;#8217;t like and who wants to establish a white ethno state a&amp;nbsp;Nazi?&lt;/p&gt;
&lt;p&gt;Calling someone a Nazi — or even a white supremacist — is an &lt;em&gt;attack&lt;/em&gt;, you see.  Merely expressing the desire that people of color not exist is perfectly peaceful, but identifying the sentiment for what it is causes visible discord, which is&amp;nbsp;unacceptable.&lt;/p&gt;
&lt;p&gt;These clowns even know this sort of thing and &lt;a href="https://twitter.com/ContraPoints/status/896823834338263041"&gt;strategize around it&lt;/a&gt;.  Or, try, at least.  Maybe it wasn&amp;#8217;t that successful this weekend — though flicking through Charlottesville headlines now, they seem to be relatively tame in how they refer to the&amp;nbsp;ralliers.&lt;/p&gt;
&lt;p&gt;I&amp;#8217;m reminded of a group of furries — the &lt;em&gt;alt-furries&lt;/em&gt; — who have been espousing white supremacy and wearing red armbands with a white circle containing a black&amp;#8230;  &lt;em&gt;pawprint&lt;/em&gt;.  Ah, yes, that&amp;#8217;s completely&amp;nbsp;different.&lt;/p&gt;
&lt;hr /&gt;
&lt;p&gt;So, what to do about&amp;nbsp;this?&lt;/p&gt;
&lt;p&gt;&lt;span class="dquo"&gt;&amp;#8220;&lt;/span&gt;&lt;a href="https://twitter.com/splcenter/status/896370696158400512"&gt;Ignore them&lt;/a&gt;&amp;#8221; is a popular option, often espoused to bullied children by parents who have never been bullied, shortly before they resume complaining about passive-aggressive office politics.  The trouble with ignoring them is that, just like in smaller communities, they have a tendency to &lt;em&gt;fester&lt;/em&gt;.  They take over large chunks of influential Internet surface area like 4chan and Reddit; they help get an inept buffoon elected; and then they start to have torch-bearing rallies and run people over with&amp;nbsp;cars.&lt;/p&gt;
&lt;p&gt;4chan illustrates a kind of corollary here.  Anyone who&amp;#8217;s steeped in Internet Culture™ is surely familiar with 4chan; I was never a regular visitor, but it had enough influence that I was still &lt;em&gt;aware&lt;/em&gt; of it and some of its culture.  It was always thick with irony, which grew into a sort of ironic detachment — perhaps one of the major sources of the recurring online trope that having feelings is bad — which proceeded into ironic&amp;nbsp;racism.&lt;/p&gt;
&lt;p&gt;And now the ironic racism is indistinguishable from actual racism, as tends to be the case.  Do they &amp;#8220;actually&amp;#8221; &amp;#8220;mean it&amp;#8221;, or are they just trying to get a rise out of people?  What the hell is &lt;em&gt;unironic&lt;/em&gt; racism if not trying to get a rise out of people?  What difference is there to onlookers, especially as they move to become increasingly involved with&amp;nbsp;politics?&lt;/p&gt;
&lt;p&gt;&lt;span class="dquo"&gt;&amp;#8220;&lt;/span&gt;It&amp;#8217;s just a joke&amp;#8221; and &amp;#8220;it was just a thoughtless comment&amp;#8221; are exceptionally common defenses made by people desperate to preserve the illusion of harmony, but the strain of overt white supremacy currently running rampant through the &lt;span class="caps"&gt;US&lt;/span&gt; was &lt;em&gt;built on those excuses&lt;/em&gt;.&lt;/p&gt;
&lt;hr /&gt;
&lt;p&gt;The other favored option is to &lt;em&gt;debate them&lt;/em&gt;, to defeat their ideas with better&amp;nbsp;ideas.&lt;/p&gt;
&lt;p&gt;Well, hang on.  What are their ideas, again?  I hear they were chanting stuff like &amp;#8220;go back to Africa&amp;#8221; and &amp;#8220;fuck you, faggots&amp;#8221;.  Given that this was an overtly political rally (and again, the &lt;em&gt;Nazi fucking regalia&lt;/em&gt;), I don&amp;#8217;t think it&amp;#8217;s a far cry to describe their ideas as &amp;#8220;let&amp;#8217;s get rid of black people and queer&amp;nbsp;folks&amp;#8221;.&lt;/p&gt;
&lt;p&gt;This is an underlying proposition: that white supremacy is inherently violent.  After all, if the alt-right seized total political power, what would they do with it?  If I asked the same question of Democrats or Republicans, I&amp;#8217;d imagine answers like &amp;#8220;universal health care&amp;#8221; or &amp;#8220;screw over poor people&amp;#8221;.  But people whose primary goal is to have a country full of only white folks?  What are they going to do, &lt;em&gt;politely ask&lt;/em&gt; everyone else to leave?  They&amp;#8217;re invoking the memory of people who &lt;em&gt;committed genocide&lt;/em&gt; and also &lt;em&gt;tried to take over the fucking world&lt;/em&gt;.  They are &lt;em&gt;outright saying&lt;/em&gt;, these are the people we look up to, this is who we think had a great&amp;nbsp;idea.&lt;/p&gt;
&lt;p&gt;How, precisely, does one defeat these ideas with rational&amp;nbsp;debate?&lt;/p&gt;
&lt;p&gt;Because the underlying core philosophy beneath all this is: &amp;#8220;it would be good for me if everything were about me&amp;#8221;.  And &lt;strong&gt;that&amp;#8217;s true&lt;/strong&gt;!  (Well, it probably wouldn&amp;#8217;t work out how they imagine in practice, but it&amp;#8217;s true &lt;em&gt;enough&lt;/em&gt;.)  Consider that slavery is probably fantastic &lt;em&gt;if you&amp;#8217;re the one with the slaves&lt;/em&gt;; the issue is that it&amp;#8217;s &lt;em&gt;reprehensible&lt;/em&gt;, not that the very notion contains some kind of 101-level logical fallacy.  That&amp;#8217;s probably why we &lt;em&gt;had a fucking war over it&lt;/em&gt; instead of hashing it out over&amp;nbsp;brunch.&lt;/p&gt;
&lt;p&gt;&amp;#8230;except we &lt;em&gt;did&lt;/em&gt; hash it out over brunch once, and the result was that slavery was still allowed but slaves only counted as 60% of a person for the sake of counting how much political power states got.  So that&amp;#8217;s how rational debate worked out.  I&amp;#8217;m sure the slaves were thrilled with that&amp;nbsp;progress.&lt;/p&gt;
&lt;hr /&gt;
&lt;p&gt;That really only leaves &lt;em&gt;pushing back&lt;/em&gt;, which raises the question of how to push&amp;nbsp;back.&lt;/p&gt;
&lt;p&gt;And, I don&amp;#8217;t know.  Pushing back is much harder in spaces you don&amp;#8217;t control, spaces you&amp;#8217;re already struggling to justify your own presence in.  For most people, that&amp;#8217;s most spaces.  It&amp;#8217;s made all the harder by that tendency to preserve illusory peace; even the tamest request that someone knock off some odious behavior can be met by pushback, even by third&amp;nbsp;parties.&lt;/p&gt;
&lt;p&gt;At the same time, I&amp;#8217;m aware that white supremacists prey on disillusioned young white dudes who feel like they don&amp;#8217;t fit in, who were promised the world and inherited kind of a mess.  Does criticism drive them further away?  The alt-right also opposes &amp;#8220;political correctness&amp;#8221;, i.e. &amp;#8220;not being a fucking&amp;nbsp;asshole&amp;#8221;.&lt;/p&gt;
&lt;p&gt;God knows we all suck at this kind of behavior correction, even within our own in-groups.  Fandoms have become almost ridiculously vicious as platforms like Twitter and Tumblr &lt;a href="https://eev.ee/blog/2016/02/15/everyones-offended-these-days/"&gt;amplify individual anger to deafening levels&lt;/a&gt;.  It probably doesn&amp;#8217;t help that we&amp;#8217;re all just &lt;em&gt;exhausted&lt;/em&gt;, that every new fuck-up feels like it bears the same weight as the last hundred&amp;nbsp;combined.&lt;/p&gt;
&lt;p&gt;This is the part where I admit I don&amp;#8217;t know anything about people and don&amp;#8217;t have any easy answers.&amp;nbsp;Surprise!&lt;/p&gt;
&lt;hr /&gt;
&lt;p&gt;The other alternative is, well, &lt;em&gt;punching Nazis&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;That meme kind of haunts me.  It raises really fucking complicated questions about when violence is acceptable, in a culture that&amp;#8217;s completely incapable of answering&amp;nbsp;them.&lt;/p&gt;
&lt;p&gt;America&amp;#8217;s relationship to violence is so bizarre and two-faced as to be almost incomprehensible.  We &lt;em&gt;worship&lt;/em&gt; it.  We have the biggest military in the world by an almost comical margin.  It&amp;#8217;s fairly mainstream to own deadly weapons for the express stated purpose of armed revolution against the government, should that become necessary, where &amp;#8220;necessary&amp;#8221; is left ominously undefined.  Our movies are about explosions and beating up bad guys; our video games are about explosions and shooting bad guys.  We fantasize about solving foreign policy problems by nuking someone — hell, our talking heads are currently in polite discussion about whether we should nuke North Korea and annihilate up to twenty-five million people, as punishment for daring to have the bomb that only &lt;em&gt;we&amp;#8217;re&lt;/em&gt; allowed to&amp;nbsp;have.&lt;/p&gt;
&lt;p&gt;But&amp;#8230;  violence is&amp;nbsp;bad.&lt;/p&gt;
&lt;p&gt;That&amp;#8217;s about as far as the other side of the coin gets.  It&amp;#8217;s bad.  We condemn it in the strongest possible terms.  Also, we probably bombed someone&amp;nbsp;today.&lt;/p&gt;
&lt;p&gt;I observe that the one time Nazis &lt;em&gt;were&lt;/em&gt; a serious threat, America was happy to let them try to take over the world until their allies finally showed up on our back&amp;nbsp;porch.&lt;/p&gt;
&lt;p&gt;Maybe I don&amp;#8217;t understand what &amp;#8220;violence&amp;#8221; means.  In a quest to find out why people are talking about &amp;#8220;leftist violence&amp;#8221; lately, I found a &lt;a href="http://www.nationalreview.com/article/447253/violence-left-campus-streets"&gt;National Review article from May&lt;/a&gt; that twice suggests blocking traffic is a form of violence.  Anarchists have smashed some windows and set a couple fires at protests this year — and, hey, please knock that crap off? — which is called violence against, I guess, Starbucks.  Black Lives Matter could be throwing a birthday party and Twitter would still be abuzz with people calling them&amp;nbsp;thugs.&lt;/p&gt;
&lt;p&gt;Meanwhile, there&amp;#8217;s a trend of murderers with increasingly overt links to the alt-right, and everyone is still handling them with kid gloves.  First it was murders by people repeating their talking points; now it&amp;#8217;s the culmination of a torches-and-pitchforks mob.  (Ah, sorry, not pitchforks; &lt;em&gt;assault rifles&lt;/em&gt;.)  And we still get this incredibly bizarre both-sides-ism, a White House that refers to the people who &lt;em&gt;didn&amp;#8217;t&lt;/em&gt; murder anyone as &amp;#8220;&lt;a href="https://twitter.com/gabrielsherman/status/896714418490355714"&gt;just as violent if not more so&lt;/a&gt;&amp;#8220;.&lt;/p&gt;
&lt;hr /&gt;
&lt;p&gt;Should you punch Nazis?  I don&amp;#8217;t know.  All I know is that I&amp;#8217;m extremely dissatisfied with discourse that&amp;#8217;s extremely alarmed by hypothetical punches — far more mundane than what you&amp;#8217;d see after a sporting event — but treats a push for ethnic cleansing as a mere difference of&amp;nbsp;opinion.&lt;/p&gt;
&lt;p&gt;The equivalent to a punch in an online space is probably banning, which is almost laughable in comparison.  It doesn&amp;#8217;t cause physical harm, but it &lt;em&gt;is&lt;/em&gt; a use of concrete force.  Doesn&amp;#8217;t pose quite the same moral quandary,&amp;nbsp;though.&lt;/p&gt;
&lt;p&gt;Somewhere in the middle is the currently popular pastime of doxxing (doxxxxxxing) people spotted at the rally in an attempt to get them fired or whatever.  Frankly, that skeeves me out, though apparently not enough that I&amp;#8217;m directly chastizing anyone for&amp;nbsp;it.&lt;/p&gt;
&lt;hr /&gt;
&lt;p&gt;We aren&amp;#8217;t really equipped, as a society, to deal with memetic threats.  We aren&amp;#8217;t even equipped to determine what they &lt;em&gt;are&lt;/em&gt;.  We had a fucking &lt;em&gt;world war&lt;/em&gt; over this, and now people are outright saying &amp;#8220;hey I&amp;#8217;m like those people we went and killed a lot in that world war&amp;#8221; and we give them interviews and compliment their fashion&amp;nbsp;sense.&lt;/p&gt;
&lt;p&gt;A looming question is always, &lt;em&gt;what if they then do it to you?&lt;/em&gt;  What if people try to get &lt;em&gt;you&lt;/em&gt; fired, to punch &lt;em&gt;you&lt;/em&gt; for your&amp;nbsp;beliefs?&lt;/p&gt;
&lt;p&gt;I think about that a lot, and then I remember that it&amp;#8217;s perfectly legal to fire someone for being gay in half the country.  (Courts are currently wrangling whether Title &lt;span class="caps"&gt;VII&lt;/span&gt; forbids this, but with the current administration, I&amp;#8217;m not optimistic.)  I know people who&amp;#8217;ve been fired for coming out as trans.  I doubt I&amp;#8217;d have to look very far to find someone who&amp;#8217;s been punched for either&amp;nbsp;reason.&lt;/p&gt;
&lt;p&gt;And these aren&amp;#8217;t even beliefs; they&amp;#8217;re just properties of a person.  You can &lt;em&gt;stop&lt;/em&gt; being a white supremacist, one of those people yelling &amp;#8220;fuck you,&amp;nbsp;faggots&amp;#8221;.&lt;/p&gt;
&lt;p&gt;So I have to recuse myself from this asinine question, because I can&amp;#8217;t fairly judge the risk of retaliation when it already happens to people I care&amp;nbsp;about.&lt;/p&gt;
&lt;p&gt;Meanwhile, if a white supremacist &lt;em&gt;does&lt;/em&gt; get punched, I absolutely still want my tax dollars to pay for their universal&amp;nbsp;healthcare.&lt;/p&gt;
&lt;hr /&gt;
&lt;p&gt;The same wrinkle comes up with free speech, which is &lt;em&gt;paramount&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;The &lt;span class="caps"&gt;ACLU&lt;/span&gt; &lt;a href="https://twitter.com/ACLU/status/896508486976131073"&gt;reminds us&lt;/a&gt; that the First Amendment &amp;#8220;protects vile, hateful, and ignorant speech&amp;#8221;.  I think they&amp;#8217;ve forgotten that that&amp;#8217;s a &lt;em&gt;side effect&lt;/em&gt;, not the &lt;em&gt;goal&lt;/em&gt;.  No one sat down and suggested that protecting vile speech was some kind of noble cause, yet that&amp;#8217;s how we seem to be treating&amp;nbsp;it.&lt;/p&gt;
&lt;p&gt;The &lt;em&gt;point&lt;/em&gt; was to avoid a situation where &lt;em&gt;the government&lt;/em&gt; is arbitrarily deciding what qualifies as vile, hateful, and ignorant, and was using that power to eliminate ideas distasteful to politicians.  You know, like, hypothetically, if they interrogated and jailed a bunch of people for supporting the wrong economic system.  Or convicted someone under the Espionage Act for &lt;a href="https://en.wikipedia.org/wiki/Schenck_v._United_States"&gt;opposing the draft&lt;/a&gt;.  (Hey, that&amp;#8217;s where the &amp;#8220;shouting fire in a crowded theater&amp;#8221; line comes&amp;nbsp;from.)&lt;/p&gt;
&lt;p&gt;But these are ideas that are already &lt;em&gt;in the government&lt;/em&gt;.  Bannon, a man who was chair of a news organization he himself called &amp;#8220;the platform for the alt-right&amp;#8221;, has the President&amp;#8217;s ear!  How much more mainstream can you&amp;nbsp;get?&lt;/p&gt;
&lt;p&gt;So again I&amp;#8217;m having a little trouble balancing &amp;#8220;we need to defend the free speech of white supremacists or risk losing it for everyone&amp;#8221; against &amp;#8220;we fairly recently were ferreting out communists and the lingering public perception is that communists are scary, not that the government&amp;nbsp;is&amp;#8221;.&lt;/p&gt;
&lt;hr /&gt;
&lt;p&gt;This isn&amp;#8217;t to say that freedom of speech is bad, only that the way we talk about it has become &lt;em&gt;fanatical&lt;/em&gt; to the point of absurdity.  We love it so much that we turn around and try to apply it to corporations, to platforms, to communities, to interpersonal&amp;nbsp;relationships.&lt;/p&gt;
&lt;p&gt;Look at 4chan.  It&amp;#8217;s completely public and anonymous; you only get banned for putting the functioning of the site itself in jeopardy.  &lt;em&gt;Nothing&lt;/em&gt; is stopping a larger group of people from joining its politics board and tilting sentiment the other way — except that the current population is so odious that no one wants to be around them.  Everyone else has evaporated away, as tends to&amp;nbsp;happen.&lt;/p&gt;
&lt;p&gt;Free speech is great for a &lt;em&gt;government&lt;/em&gt;, to prevent quashing politics that threaten the status quo (except it&amp;#8217;s a joke and they&amp;#8217;ll do it anyway).  People can&amp;#8217;t very readily just bail when the government doesn&amp;#8217;t like them, anyway.  It&amp;#8217;s also nice to keep in mind to some degree for ubiquitous &lt;em&gt;platforms&lt;/em&gt;.  But the smaller you go, the easier it is for people to evaporate away, and the faster pure free speech will turn the place to crap.  You&amp;#8217;ll be left only with people who care about&amp;nbsp;nothing.&lt;/p&gt;
&lt;hr /&gt;
&lt;p&gt;At the very least, it seems clear that the goal of white supremacists is some form of &lt;em&gt;destabilization&lt;/em&gt;, of disruption to the fabric of a community for purely selfish purposes.  And &lt;em&gt;those&lt;/em&gt; are the kinds of people you want to get rid of as quickly as&amp;nbsp;possible.&lt;/p&gt;
&lt;p&gt;Usually this is hard, because they act just nicely enough to create some plausible deniability.  But damn, if someone is outright &lt;em&gt;telling&lt;/em&gt; you they love Hitler, maybe skip the principled hand-wringing and focus on ejecting&amp;nbsp;them.&lt;/p&gt;</content><category term="articles"></category><category term="culture"></category><category term="patreon"></category></entry><entry><title>Growing up alongside tech</title><link href="https://eev.ee/blog/2017/08/09/growing-up-alongside-tech/" rel="alternate"></link><published>2017-08-09T08:18:00-07:00</published><updated>2017-08-09T08:18:00-07:00</updated><author><name>Eevee</name></author><id>tag:eev.ee,2017-08-09:/blog/2017/08/09/growing-up-alongside-tech/</id><summary type="html">&lt;p&gt;&lt;a href="https://www.patreon.com/user/creators?u=199476"&gt;IndustrialRobot&lt;/a&gt; asks…  or, uh, asked last month:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;industrialrobot: How has your views on tech changed as you’ve got older?&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;This is so open-ended that it’s actually stumped me for a solid month.  I’ve had a surprisingly hard time figuring out where to even start.&lt;/p&gt;
</summary><content type="html">&lt;p&gt;&lt;a href="https://www.patreon.com/user/creators?u=199476"&gt;IndustrialRobot&lt;/a&gt; asks&amp;#8230;  or, uh, asked last&amp;nbsp;month:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;industrialrobot: How has your views on tech changed as you&amp;#8217;ve got&amp;nbsp;older?&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;This is so open-ended that it&amp;#8217;s actually stumped me for a solid month.  I&amp;#8217;ve had a surprisingly hard time figuring out where to even&amp;nbsp;start.&lt;/p&gt;


&lt;hr /&gt;
&lt;p&gt;It&amp;#8217;s not that my views of tech have changed too &lt;em&gt;much&lt;/em&gt; — it&amp;#8217;s that they&amp;#8217;ve changed very &lt;em&gt;gradually&lt;/em&gt;.  Teasing out and explaining any one particular change is tricky when it happened invisibly over the course of 10+&amp;nbsp;years.&lt;/p&gt;
&lt;p&gt;I think a better framework for this is to consider how my &lt;em&gt;relationship&lt;/em&gt; to tech has changed.  It&amp;#8217;s gone through three pretty distinct phases, each of which has strongly colored how I feel and talk about&amp;nbsp;technology.&lt;/p&gt;
&lt;h2 id="act-i"&gt;&lt;a class="toclink" href="#act-i"&gt;Act I&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;In which I start from&amp;nbsp;nothing.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Nothing&lt;/em&gt; is an interesting starting point.  You only really get to start there&amp;nbsp;once.&lt;/p&gt;
&lt;p&gt;Learning something on my own as a kid was something of a magical experience, in a way that I don&amp;#8217;t think I could replicate as an adult.  I liked computers; I liked toying with computers; so I did&amp;nbsp;that.&lt;/p&gt;
&lt;p&gt;I don&amp;#8217;t know how universal this is, but when I was a kid, I couldn&amp;#8217;t even &lt;em&gt;conceive&lt;/em&gt; of how incredible things were made.  Buildings?  Cars?  Paintings?  Operating systems?  Where does any of that come from?  Obviously &lt;em&gt;someone&lt;/em&gt; made them, but it&amp;#8217;s not the sort of philosophical point I lingered on when I was 10, so in the back of my head they basically just appeared fully-formed from the&amp;nbsp;æther.&lt;/p&gt;
&lt;p&gt;That meant that when I started trying out programming, I had &lt;em&gt;no aspirations&lt;/em&gt;.  I couldn&amp;#8217;t imagine how far I would go, because all the &lt;em&gt;examples&lt;/em&gt; of how far I would go were completely disconnected from any idea of human achievement.  I started out with &lt;a href="https://eev.ee/blog/2016/04/05/my-first-computer/"&gt;&lt;span class="caps"&gt;BASIC&lt;/span&gt; on a toy computer&lt;/a&gt;; how could I possibly envision a connection between that and something like a mainstream video game?  Every new thing felt like a new form of magic, so I couldn&amp;#8217;t conceive that I was even in the same ballpark as whatever process produced &lt;em&gt;real&lt;/em&gt; software.  (Even seeing the source code&amp;nbsp;for &lt;code&gt;GORILLAS.BAS&lt;/code&gt;, it didn&amp;#8217;t quite click.  I didn&amp;#8217;t think to try &lt;em&gt;reading&lt;/em&gt; any of it until years after I&amp;#8217;d first encountered the&amp;nbsp;game.)&lt;/p&gt;
&lt;p&gt;This isn&amp;#8217;t to say I didn&amp;#8217;t have &lt;em&gt;goals&lt;/em&gt;.  I invented goals constantly, as I&amp;#8217;ve always done; as soon as I learned about a new thing, I&amp;#8217;d imagine some ways to use it, then try to build them.  I produced a lot of little weird goofy toys, some of which entertained my tiny friend group for a couple days, some of which never saw the light of day.  But none of it felt like steps along the way to some mountain peak of mastery, because I didn&amp;#8217;t realize the mountain peak was even a place that could be gone to.  It was pure, unadulterated (!)&amp;nbsp;playing.&lt;/p&gt;
&lt;p&gt;I contrast this to my art career, which started only a couple years ago.  I was already in my late 20s, so I&amp;#8217;d already spend decades &lt;em&gt;seeing&lt;/em&gt; a very broad spectrum of art: everything from quick sketches up to painted masterpieces.  And I&amp;#8217;d seen the &lt;em&gt;people&lt;/em&gt; who create that art, sometimes seen them create it in real-time.  I&amp;#8217;m even in a relationship with one of them!  And of course I&amp;#8217;d already had the experience of advancing through tech stuff and discovering first-hand that even the most amazing software is still &lt;em&gt;just code someone wrote&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;So from the very beginning, from the moment I touched pencil to paper, I &lt;em&gt;knew&lt;/em&gt; the possibilities.  I &lt;em&gt;knew&lt;/em&gt; that the goddamn Sistine Chapel was something I could learn to do, if I were willing to put enough time in — and I knew that I&amp;#8217;m not, so I&amp;#8217;d have to settle somewhere a ways before that.  I &lt;em&gt;knew&lt;/em&gt; that I&amp;#8217;d have to put an awful lot of work in before I&amp;#8217;d be producing anything very&amp;nbsp;impressive.&lt;/p&gt;
&lt;p&gt;I did it anyway (though perhaps waited longer than necessary to start), but those aren&amp;#8217;t things I can &lt;em&gt;un-know&lt;/em&gt;, and so I can never truly explore art from a place of pure ignorance.  On the other hand, I&amp;#8217;ve probably &lt;a href="https://eev.ee/blog/2016/05/06/learning-to-draw-learning-to-learn/"&gt;learned to draw&lt;/a&gt; much more quickly and efficiently than if I&amp;#8217;d done it as a kid, precisely &lt;em&gt;because&lt;/em&gt; I know those things.  Now I can decide I want to do something far beyond my current abilities, then go figure out how to do it.  When I was just &lt;em&gt;playing&lt;/em&gt;, that kind of ambition was&amp;nbsp;impossible.&lt;/p&gt;
&lt;hr /&gt;
&lt;p&gt;So, I&amp;nbsp;played.&lt;/p&gt;
&lt;p&gt;How did this affect my views on tech?  Well, I didn&amp;#8217;t&amp;#8230;  &lt;em&gt;have&lt;/em&gt; any.  Learning by playing tends to teach you things in an outward sprawl without many abrupt jumps to new areas, so you don&amp;#8217;t tend to run up against conflicting information.  The whole point of opinions is that they&amp;#8217;re your own resolution to a conflict; without conflict, I can&amp;#8217;t meaningfully say I had any opinions.  I just accepted whatever I encountered at face value, because I didn&amp;#8217;t even know enough to suspect there could be alternatives&amp;nbsp;yet.&lt;/p&gt;
&lt;h2 id="act-ii"&gt;&lt;a class="toclink" href="#act-ii"&gt;Act II&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;That started to seriously change around, I suppose, the end of high school and beginning of college.  I was becoming aware of this whole &amp;#8220;open source&amp;#8221; concept.  I took classes that used languages I wouldn&amp;#8217;t otherwise have given a second thought.  (One of them was Python!)  I started to contribute to other people&amp;#8217;s projects.  Eventually I even got a job, where I &lt;em&gt;had&lt;/em&gt; to work with &lt;em&gt;other people&lt;/em&gt;.  It probably also helped that I&amp;#8217;d had to maintain my own old code a few&amp;nbsp;times.&lt;/p&gt;
&lt;p&gt;Now I was faced with conflicting subjective ideas, and I had to form opinions about them!  And so I did.  With &lt;em&gt;gusto&lt;/em&gt;.  Over time, I developed an idea of what was &lt;em&gt;Right&lt;/em&gt; based on experience I&amp;#8217;d accrued.  And then I set out to always do things &lt;em&gt;Right&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;That&amp;#8217;s served me decently well with some individual problems, but it also led me to inflict a lot of unnecessary pain on myself.  Several endeavors languished for no other reason than my dissatisfaction with the &lt;em&gt;architecture&lt;/em&gt;, long before the basic functionality was done.  I started a number of &amp;#8220;pure&amp;#8221; projects around this time, generic tools like imaging libraries that I had no direct need for.  I built them for the sake of them, I guess because I felt like I was improving some niche&amp;#8230;  but of course I never finished any.  It was always in areas I didn&amp;#8217;t know that well in the first place, which is a fine way to learn if you have a specific concrete goal in mind — but it turns out that building a generic library for editing images means you have to know &lt;em&gt;everything&lt;/em&gt; about images.  Perhaps that ambition went a little&amp;nbsp;haywire.&lt;/p&gt;
&lt;p&gt;I&amp;#8217;ve said &lt;a href="https://eev.ee/blog/2016/06/12/one-year-later/"&gt;before&lt;/a&gt; that this sort of (self-inflicted!) work was unfulfilling, in part because the best outcome would be that a few distant programmers&amp;#8217; lives are slightly easier.  I do still think that, but I think there&amp;#8217;s a deeper point here&amp;nbsp;too.&lt;/p&gt;
&lt;p&gt;In forgetting how to play, I&amp;#8217;d stopped putting any of &lt;em&gt;myself&lt;/em&gt; in most of the work I was doing.  Yes, building an imaging library is kind of a slog that &lt;em&gt;someone&lt;/em&gt; has to do, but&amp;#8230;  I assume the people who work on software like &lt;span class="caps"&gt;PIL&lt;/span&gt; and ImageMagick are &lt;em&gt;actually interested in it&lt;/em&gt;.  The few domains I tried to enter and revolutionize weren&amp;#8217;t &lt;em&gt;passions&lt;/em&gt; of mine; I just happened to walk through the neighborhood one day and decided I could obviously do it&amp;nbsp;better.&lt;/p&gt;
&lt;p&gt;Not coincidentally, this was the same era of my life that led me to write stuff like that &lt;span class="caps"&gt;PHP&lt;/span&gt; post, which you may notice I am conspicuously not even linking to.  I don&amp;#8217;t think I would write anything like it nowadays.  I could see myself approaching the same &lt;em&gt;subject&lt;/em&gt;, but purely from the point of view of language design, with more contrasts and tradeoffs and less going for volume.  I certainly wouldn&amp;#8217;t lead off with inflammatory puffery like &amp;#8220;&lt;span class="caps"&gt;PHP&lt;/span&gt; is a community of&amp;nbsp;amateurs&amp;#8221;.&lt;/p&gt;
&lt;h3 id="act-iii"&gt;&lt;a class="toclink" href="#act-iii"&gt;Act III&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;I &lt;em&gt;think&lt;/em&gt; I&amp;#8217;ve mellowed out a good bit in the last few&amp;nbsp;years.&lt;/p&gt;
&lt;p&gt;It turns out that being &lt;em&gt;Right&lt;/em&gt; is much less important than being &lt;em&gt;Not Wrong&lt;/em&gt; — i.e., rather than trying to make something perfect that can be adapted to any future case, just avoid as many pitfalls as possible.  Code that does something useful has much more practical value than unfinished code with some pristine&amp;nbsp;architecture.&lt;/p&gt;
&lt;p&gt;Nowhere is this more apparent than in game development, where all code is doomed to be crap and the best you can hope for is to stem the tide.  But there&amp;#8217;s also a fixed &lt;em&gt;goal&lt;/em&gt; that&amp;#8217;s completely unrelated to how the code looks: does the game work, and is it fun to play?  Yes?  Ship the damn thing and forget about&amp;nbsp;it.&lt;/p&gt;
&lt;p&gt;Games are also nice because it&amp;#8217;s very easy to pour my own feelings into them and evoke feelings in the people who play them.  They&amp;#8217;re &lt;em&gt;mine&lt;/em&gt;, something with my fingerprints on them — even the &lt;a href="https://eevee.itch.io/"&gt;games&lt;/a&gt; I&amp;#8217;ve built with glip have plenty of my own hallmarks, little touches I added on a whim or attention to specific details that I care&amp;nbsp;about.&lt;/p&gt;
&lt;p&gt;Maybe a better example is the Doom map parser I started writing.  It sounds like a &amp;#8220;pure&amp;#8221; problem again, except that I actually know an awful lot about the subject already!  I also cleverly (accidentally) released some useful &lt;em&gt;results&lt;/em&gt; of the work I&amp;#8217;ve done thusfar — like statistics about Doom &lt;span class="caps"&gt;II&lt;/span&gt; maps and a few screenshots of flipped stock maps — even though I don&amp;#8217;t think the parser itself is far enough along to release yet.  The tool has served a purpose, one with my fingerprints on it, even without being released publicly.  That keeps it fresh in my mind as something interesting I&amp;#8217;d like to keep working on, eventually.  (When I run into an architecture question, I step back for a while, or I do other work in the hopes that the solution will reveal&amp;nbsp;itself.)&lt;/p&gt;
&lt;p&gt;I also made two simple Pokémon &lt;span class="caps"&gt;ROM&lt;/span&gt; hacks this year, despite knowing nothing about Game Boy internals or assembly when I started.  I just decided I wanted to do an open-ended thing beyond my reach, and I went to do it, not worrying about cleanliness and willing to accept a bumpy ride to get there.  I &lt;em&gt;played&lt;/em&gt;, but in a more experienced way, invoking the stuff I know (and the people I&amp;#8217;ve met!) to help me get a running start in completely unfamiliar&amp;nbsp;territory.&lt;/p&gt;
&lt;hr /&gt;
&lt;p&gt;This feels like a really fine distinction that I&amp;#8217;m not sure I&amp;#8217;m doing justice.  I don&amp;#8217;t know if I could&amp;#8217;ve appreciated it three or four years ago.  But I missed making toys, and I&amp;#8217;m glad I&amp;#8217;m doing it&amp;nbsp;again.&lt;/p&gt;
&lt;p&gt;In short, I forgot how to have fun with programming for a little while, and I&amp;#8217;ve finally started to figure it out again.  And that&amp;#8217;s far more important than whether you use &lt;span class="caps"&gt;PHP&lt;/span&gt; or&amp;nbsp;not.&lt;/p&gt;</content><category term="articles"></category><category term="patreon"></category></entry><entry><title>Some memorable levels</title><link href="https://eev.ee/blog/2017/07/01/some-memorable-levels/" rel="alternate"></link><published>2017-07-01T16:27:00-07:00</published><updated>2017-07-01T16:27:00-07:00</updated><author><name>Eevee</name></author><id>tag:eev.ee,2017-07-01:/blog/2017/07/01/some-memorable-levels/</id><summary type="html">&lt;p&gt;Another &lt;a href="https://www.patreon.com/eevee"&gt;Patreon&lt;/a&gt; request from &lt;a href="https://www.patreon.com/user?u=2491881"&gt;Nova Dasterin&lt;/a&gt;:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Maybe something about level design. In relation to a vertical shmup since I’m working on one of those.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;I’ve been thinking about level design a lot lately, seeing as how I’ve started…  designing levels.  Shmups are probably the genre I’m the &lt;em&gt;worst&lt;/em&gt; at, but perhaps some general principles will apply universally.&lt;/p&gt;
&lt;p&gt;And speaking of general principles, that’s something I’ve been thinking about too.&lt;/p&gt;
&lt;p&gt;I’ve been struggling to create a more expansive tileset for a platformer, due to two general problems: figuring out what I want to show, and figuring out how to show it with a limited size and palette.  I’ve been browsing through a lot of pixel art from games I remember fondly in the hopes of finding some inspiration, but so far all I’ve done is very nearly copy a dirt tile someone submitted to my potluck project.&lt;/p&gt;
&lt;p&gt;Recently I realized that I might have been going about &lt;em&gt;looking for inspiration&lt;/em&gt; all wrong.  I’ve been sifting through stuff in the hopes of finding something that would create some flash of enlightenment, but so far that aimless tourism has only found me a thing or two to copy.&lt;/p&gt;
&lt;p&gt;I don’t want to copy a small chunk of the final product; I want to &lt;em&gt;understand&lt;/em&gt; the underlying ideas that led the artist to create what they did in the first place.  Or, no, that’s not quite right either.  I don’t want someone else’s ideas; I want to identify what &lt;em&gt;I&lt;/em&gt; like, figure out &lt;em&gt;why I like it&lt;/em&gt;, and turn that into some kinda of general design idea.  Find the underlying themes that appeal to me and figure out some principles that I could apply.  You know, examine stuff critically.&lt;/p&gt;
&lt;p&gt;I haven’t had time to take a deeper look at pixel art this way, so I’ll try it right now with level design.  Here, then, are some levels from various games that stand out to me for whatever reason; the feelings they evoke when I think about them; and my best effort at unearthing some design principles from those feelings.&lt;/p&gt;
</summary><content type="html">&lt;p&gt;Another &lt;a href="https://www.patreon.com/eevee"&gt;Patreon&lt;/a&gt; request from &lt;a href="https://www.patreon.com/user?u=2491881"&gt;Nova Dasterin&lt;/a&gt;:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Maybe something about level design. In relation to a vertical shmup since I&amp;#8217;m working on one of&amp;nbsp;those.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;I&amp;#8217;ve been thinking about level design a lot lately, seeing as how I&amp;#8217;ve started&amp;#8230;  designing levels.  Shmups are probably the genre I&amp;#8217;m the &lt;em&gt;worst&lt;/em&gt; at, but perhaps some general principles will apply&amp;nbsp;universally.&lt;/p&gt;
&lt;p&gt;And speaking of general principles, that&amp;#8217;s something I&amp;#8217;ve been thinking about&amp;nbsp;too.&lt;/p&gt;
&lt;p&gt;I&amp;#8217;ve been struggling to create a more expansive tileset for a platformer, due to two general problems: figuring out what I want to show, and figuring out how to show it with a limited size and palette.  I&amp;#8217;ve been browsing through a lot of pixel art from games I remember fondly in the hopes of finding some inspiration, but so far all I&amp;#8217;ve done is very nearly copy a dirt tile someone submitted to my potluck&amp;nbsp;project.&lt;/p&gt;
&lt;p&gt;Recently I realized that I might have been going about &lt;em&gt;looking for inspiration&lt;/em&gt; all wrong.  I&amp;#8217;ve been sifting through stuff in the hopes of finding something that would create some flash of enlightenment, but so far that aimless tourism has only found me a thing or two to&amp;nbsp;copy.&lt;/p&gt;
&lt;p&gt;I don&amp;#8217;t want to copy a small chunk of the final product; I want to &lt;em&gt;understand&lt;/em&gt; the underlying ideas that led the artist to create what they did in the first place.  Or, no, that&amp;#8217;s not quite right either.  I don&amp;#8217;t want someone else&amp;#8217;s ideas; I want to identify what &lt;em&gt;I&lt;/em&gt; like, figure out &lt;em&gt;why I like it&lt;/em&gt;, and turn that into some kinda of general design idea.  Find the underlying themes that appeal to me and figure out some principles that I could apply.  You know, examine stuff&amp;nbsp;critically.&lt;/p&gt;
&lt;p&gt;I haven&amp;#8217;t had time to take a deeper look at pixel art this way, so I&amp;#8217;ll try it right now with level design.  Here, then, are some levels from various games that stand out to me for whatever reason; the feelings they evoke when I think about them; and my best effort at unearthing some design principles from those&amp;nbsp;feelings.&lt;/p&gt;


&lt;h2 id="doom-ii-map10-refueling-base"&gt;&lt;a class="toclink" href="#doom-ii-map10-refueling-base"&gt;Doom II: MAP10, Refueling Base&lt;/a&gt;&lt;/h2&gt;
&lt;div class="prose-full-illustration"&gt;
&lt;img src="https://eev.ee/media/2017-07-01-level-design/doom2-refueling-base-1.png" alt="Opening view of Refueling Base, showing a descent down some stairs into a room not yet visible"&gt;
&lt;/div&gt;

&lt;p&gt;&lt;em&gt;screenshots mine — map via &lt;a href="https://doomwiki.org/wiki/MAP10:_Refueling_Base_%28Doom_II%29"&gt;doom wiki&lt;/a&gt; — see also &lt;a href="http://ian-albert.com/games/doom_2_maps/MAP10.jpg"&gt;textured perspective map&lt;/a&gt; (warning: large!) via &lt;a href="http://ian-albert.com/games/doom_2_maps/"&gt;ian albert&lt;/a&gt; — &lt;a href="https://www.youtube.com/watch?v=yU_yBamWeSo"&gt;pistol start&amp;nbsp;playthrough&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;I&amp;#8217;m surprising myself by picking Refueling Base.  I would&amp;#8217;ve expected myself to pick &lt;span class="caps"&gt;MAP08&lt;/span&gt;, Tricks and Traps, for its collection of uniquely bizarre puzzles and mechanisms.  Or &lt;span class="caps"&gt;MAP13&lt;/span&gt;, Downtown, the map that had me convinced (erroneously) that Doom levels supported multi-story structures.  Or at least &lt;span class="caps"&gt;MAP08&lt;/span&gt;, The Pit, which stands out for the unique way it &lt;em&gt;feels&lt;/em&gt; like a plunge into enemy&amp;nbsp;territory.&lt;/p&gt;
&lt;p&gt;(Curiously, those other three maps are all Sandy Petersen&amp;#8217;s sole work.  Refueling Base was started by Tom Hall in the original Doom days, then finished by Sandy for Doom &lt;span class="caps"&gt;II&lt;/span&gt;.)&lt;/p&gt;
&lt;p&gt;But Refueling Base is the level I have the most visceral reaction to: it &lt;em&gt;terrifies&lt;/em&gt;&amp;nbsp;me.&lt;/p&gt;
&lt;p&gt;See, I got into Doom &lt;span class="caps"&gt;II&lt;/span&gt; through my dad, who played it on and off sometimes.  My dad wasn&amp;#8217;t an expert gamer or anything, but as a ten-year-old, I &lt;em&gt;assumed&lt;/em&gt; he was.  I watched him play Refueling Base one night.  He died.  Again, and again, over and over.  I don&amp;#8217;t even have very strong memories of his particular attempts, but watching my &lt;em&gt;parent&lt;/em&gt; be swiftly and repeatedly defeated — at a time when I still somewhat revered parents — left enough of an impression that hearing the level music still makes my skin&amp;nbsp;crawl.&lt;/p&gt;
&lt;p&gt;This may seem strange to bring up as a first example in a post about level design, but I don&amp;#8217;t think it would have impressed on me quite so much if the level weren&amp;#8217;t designed the way it is.  (It&amp;#8217;s just a video game, of course, and since then I&amp;#8217;ve successfully beaten it from a pistol start myself.  But wow, little kid fears sure do&amp;nbsp;linger.)&lt;/p&gt;
&lt;div class="prose-full-illustration"&gt;
&lt;img src="https://eev.ee/media/2017-07-01-level-design/doom2-refueling-base-map.png" alt="Map of Refueling Base, showing multiple large rooms and numerous connections between them"&gt;
&lt;/div&gt;

&lt;p&gt;The one thing that most defines the map has to be its &lt;strong&gt;interconnected layout&lt;/strong&gt;.  Almost every major area (of which there are at least half a dozen) has at least three exits.  Not only are you rarely faced with a dead end, but you&amp;#8217;ll almost always have a &lt;em&gt;choice&lt;/em&gt; of where to go next, and that choice will lead into more&amp;nbsp;choices.&lt;/p&gt;
&lt;p&gt;This hugely informs the early combat.  Many areas near the beginning are simply adjacent with no doors between them, so it&amp;#8217;s easy for monsters to start swarming in from all directions.  It&amp;#8217;s very easy to feel overwhelmed by an endless horde; no matter where you run, they just seem to keep coming.  (In fact, Refueling Base has the most monsters of any map in the game by far: 279.  The runner up is the preceding map at 238.)  Compounding this effect is the relatively scant ammo and health in the early parts of the map; getting very far from a pistol start is an uphill&amp;nbsp;battle.&lt;/p&gt;
&lt;p&gt;The connections between rooms also yield numerous possible routes through the map, as well as several possible ways to approach any given room.  Some of the connections are secrets, which usually connect the &amp;#8220;backs&amp;#8221; of two rooms.  Clearing out one room thus rewards you with a sneaky way into another room that puts you behind all the&amp;nbsp;monsters.&lt;/p&gt;
&lt;div class="prose-full-illustration"&gt;
&lt;img src="https://eev.ee/media/2017-07-01-level-design/doom2-refueling-base-3.png" alt="Outdoor area shown from the back; a large number of monsters are lying in wait"&gt;
&lt;/div&gt;

&lt;p&gt;In fact, the map &lt;strong&gt;rewards you for exploring it&lt;/strong&gt; in&amp;nbsp;general.&lt;/p&gt;
&lt;p&gt;Well, okay.  It might be more accurate to say that that map punishes you for not exploring it.  From a pistol start, the map is surprisingly difficult — the early areas offer rather little health and ammo, and your best chance of success is a very specific route that collects weapons as quickly as possible.  Many of the most precious items are squirrelled away in (numerous!) secrets, and you&amp;#8217;ll have an especially tough time if you don&amp;#8217;t find any of them — though they tend to be&amp;nbsp;telegraphed.&lt;/p&gt;
&lt;p&gt;One particularly nasty surprise is in the area shown above, which has three small exits at the back.  Entering or leaving via any of those exits will open one of the capsule-shaped pillars, revealing even &lt;em&gt;more&lt;/em&gt; monsters.  A couple of those are pain elementals, monsters which attack by spawning another monster and shooting it at you — not something you want to be facing with the starting&amp;nbsp;pistol.&lt;/p&gt;
&lt;p&gt;But nothing about the level indicates this, so you have to make the association the hard way, probably after making several mad dashes looking for cover.  My successful attempt avoided this whole area entirely until I&amp;#8217;d found some more impressive firepower.  It&amp;#8217;s fascinating to me, because it&amp;#8217;s a fairly unique effect that doesn&amp;#8217;t make any kind of &lt;em&gt;realistic&lt;/em&gt; sense, yet it&amp;#8217;s still built out of familiar level mechanics: walk through an area and something opens up.  Almost like 2D sidescroller design logic applied to a 3D space.  I really like it, and wish I saw more of it.  So maybe that&amp;#8217;s a more interesting design idea: &lt;strong&gt;don&amp;#8217;t be afraid to do something weird only once&lt;/strong&gt;, as long as it&amp;#8217;s built out of familiar pieces so the player has a chance to make sense of&amp;nbsp;it.&lt;/p&gt;
&lt;p&gt;A similarly oddball effect is hidden in a &amp;#8220;barracks&amp;#8221; area, visible on the far right of the map.  A secret door leads to a short U-shaped hallway to a marble skull door, which is themed nothing like the rest of the room.  Opening it seems to lead back into the room you were just in, but walking &lt;em&gt;through&lt;/em&gt; the doorway teleports you to a back entrance to the boss fight at the end of the&amp;nbsp;level.&lt;/p&gt;
&lt;p&gt;It sounds so bizarre, but the telegraphing makes it seem very natural; if anything, the &amp;#8220;oh, I get it!&amp;#8221; moment overrides the weirdness.  It stops being something random and becomes something consciously designed.  I believe that this might have been built by someone, even if there&amp;#8217;s no sensible reason to have built&amp;nbsp;it.&lt;/p&gt;
&lt;p&gt;In fact, that single weird teleporter is exactly the kind of thing I&amp;#8217;d like to be better at building.  It could&amp;#8217;ve been just a plain teleporter pad, but instead it&amp;#8217;s a strange thing that adds a lot of texture to the level and makes it much more memorable.  I don&amp;#8217;t know how to even begin to have &lt;em&gt;ideas&lt;/em&gt; like that.  Maybe it&amp;#8217;s as simple as looking at mundane parts of a level and wondering: what could I do with this&amp;nbsp;instead?&lt;/p&gt;
&lt;p&gt;I think a big problem &lt;em&gt;I&lt;/em&gt; have is limiting myself to the expected and sensible, to the point that I don&amp;#8217;t even consider more outlandish ideas.  I can&amp;#8217;t shake that habit simply by bolding some text in a blog post, but maybe it would help to keep this in mind: &lt;strong&gt;you can probably get away with anything, as long as you justify it somehow&lt;/strong&gt;.  Even &amp;#8220;justify&amp;#8221; here is too strong a word; it takes only the slightest nod to make an arbitrary behavior feel like part of a world.  Why does picking up a tiny glowing knight helmet give you 1% armor in Doom?  Does anyone care?  Have you even thought about it before?  It&amp;#8217;s green and looks like armor; the bigger armor pickup is also green; yep, checks&amp;nbsp;out.&lt;/p&gt;
&lt;div class="prose-full-illustration"&gt;
&lt;img src="https://eev.ee/media/2017-07-01-level-design/doom2-refueling-base-5.png" alt="A dark and dingy concrete room full of monsters; a couple are standing under light fixtures"&gt;
&lt;/div&gt;

&lt;p&gt;On the other hand, the map as a whole ends up feeling very disorienting.  There&amp;#8217;s no shortage of landmarks, but every space is distinct in both texture and shape, so &lt;em&gt;everything&lt;/em&gt; feels like a landmark.  No one part of the map feels particularly central; there are a few candidates, but they neighbor other equally grand areas with just as many exits.  It&amp;#8217;s hard to get truly lost, but it&amp;#8217;s also hard to feel like you have a solid grasp of where everything is.  The space itself doesn&amp;#8217;t make much sense, even though small chunks of it do.  Of course, given that the Hellish parts of Doom were all just very weird overall, this is pretty&amp;nbsp;fitting.&lt;/p&gt;
&lt;p&gt;This sort of design fascinates me, because the way it feels to play is so different from the way it &lt;em&gt;looks&lt;/em&gt; as a mapper with God Vision.  Looking at the overhead map, I can identify all the familiar places easily enough, but I don&amp;#8217;t know how to &lt;em&gt;feel&lt;/em&gt; the way the map feels to play; it just looks like some rooms with doors between them.  Yet I can see screenshots and have a sense of how &amp;#8220;deep&amp;#8221; in the level they are, how difficult they are to reach, whether I want to visit or avoid them.  The lesson here might be that most of the interesting flavor of the map isn&amp;#8217;t actually contained within the overhead view; it&amp;#8217;s in the use of height and texture and&amp;nbsp;interaction.&lt;/p&gt;
&lt;div class="prose-full-illustration"&gt;
&lt;img src="https://eev.ee/media/2017-07-01-level-design/doom2-refueling-base-4.png" alt="Dark room with numerous alcoves in the walls, all of them containing a hitscan monster"&gt;
&lt;/div&gt;

&lt;p&gt;I realize as I describe all of this that I&amp;#8217;m really just describing different kinds of &lt;strong&gt;contrast&lt;/strong&gt;.  If I know one thing about creative work (and I do, I only know one thing), it&amp;#8217;s that effectively managing contrast is super duper&amp;nbsp;important.&lt;/p&gt;
&lt;p&gt;And it appears here in spades!  A brightly-lit, outdoor, wide-open round room is only a short jog away from a dark, cramped room full of right angles and alcoves.  A wide straight hallway near the beginning is directly across from a short, curvy, organic hallway.  Most of the monsters in the map are small fry, but a couple stronger critters are sprinkled here and there, and then the exit is guarded by the toughest monster in the game.  Some of the connections between rooms are simple doors; others are bizarre secret corridors or unnatural twisty&amp;nbsp;passages.&lt;/p&gt;
&lt;p&gt;You could even argue that the map has too &lt;em&gt;much&lt;/em&gt; contrast, that it starts to lose cohesion.  But if anything, I think this is one of the more cohesive maps in the first third of the game; many of the earlier maps aren&amp;#8217;t so much places as they are concepts.  This one feels distinctly like it could be &lt;em&gt;something&lt;/em&gt;.  The theming is all over the place, but enough of the parts seem&amp;nbsp;deliberate.&lt;/p&gt;
&lt;p&gt;I hadn&amp;#8217;t even thought about it until I sat down to write this post, but since this is a &amp;#8220;refueling base&amp;#8221;, I suppose those outdoor capsules (which contain green slime, inset into the floor) could be the fuel tanks!  I already referred to that dark techy area as &amp;#8220;barracks&amp;#8221;.  Elsewhere is a rather large barren room, which might be where the vehicles in need of refueling are parked?  Or is this just my imagination, and none of it was intended this&amp;nbsp;way?&lt;/p&gt;
&lt;p&gt;It doesn&amp;#8217;t really matter either way, because even in this abstract world of ambiguity and vague hints, all of those rooms still &lt;em&gt;feel like a place&lt;/em&gt;.  I don&amp;#8217;t have to know what the place &lt;em&gt;is&lt;/em&gt; for it to look internally&amp;nbsp;consistent.&lt;/p&gt;
&lt;p&gt;I&amp;#8217;m hesitant to say every game should have the loose design sense of Doom &lt;span class="caps"&gt;II&lt;/span&gt;, but it might be worth keeping in mind that &lt;strong&gt;anything can be a believable world as long as it looks consciously designed&lt;/strong&gt;.  And I&amp;#8217;d say this applies even for natural spaces — we frequently treat real-world nature as though it were &amp;#8220;designed&amp;#8221;, just with a different aesthetic&amp;nbsp;sense.&lt;/p&gt;
&lt;p&gt;Okay, okay.  I&amp;#8217;m sure I could clumsily ramble about Doom forever, but I do that enough as it is.  Other people have &lt;a href="https://www.youtube.com/playlist?list=PLEdRlER1F5rEoZ9repPcJM2jWvrXVDBSY"&gt;plenty to say&lt;/a&gt; if you&amp;#8217;re&amp;nbsp;interested.&lt;/p&gt;
&lt;p&gt;I do want to stick in one final comment about &lt;a href="https://doomwiki.org/wiki/MAP13:_Downtown_%28Doom_II%29"&gt;&lt;span class="caps"&gt;MAP13&lt;/span&gt;, Downtown&lt;/a&gt;, while I&amp;#8217;m talking about theming.  I&amp;#8217;ve seen a few people rag on it for being &amp;#8220;just a box&amp;#8221; with a lot of ideas sprinkled around — the map is basically a grid of skyscrapers, where each building has a different little mini encounter inside.  And I think that&amp;#8217;s &lt;em&gt;really cool&lt;/em&gt;, because those encounters are arranged in a way that very strongly reinforces the theme of the level, of what this place is supposed to &lt;em&gt;be&lt;/em&gt;.  It doesn&amp;#8217;t play quite like anything else in the game, simply because it was designed around a shape for flavor reasons.  &lt;strong&gt;Weird physical constraints can do interesting things to level&amp;nbsp;design.&lt;/strong&gt;&lt;/p&gt;
&lt;h2 id="braid-world-4-7-fickle-companion"&gt;&lt;a class="toclink" href="#braid-world-4-7-fickle-companion"&gt;Braid: World 4-7, Fickle Companion&lt;/a&gt;&lt;/h2&gt;
&lt;div class="prose-full-illustration"&gt;
&lt;img src="https://eev.ee/media/2017-07-01-level-design/braid-fickle-companion-1.png" alt="Simple-looking platformer level with a few ladders, a switch, and a locked door"&gt;
&lt;/div&gt;

&lt;p&gt;&lt;em&gt;screenshots via &lt;a href="https://strategywiki.org/wiki/Braid/World_4:_Time_and_Place"&gt;StrategyWiki&lt;/a&gt; — &lt;a href="https://www.youtube.com/watch?v=bfeDL4aCp-o"&gt;playthrough&lt;/a&gt; — &lt;a href="https://www.youtube.com/watch?v=vvtnek-ezuM"&gt;playthrough of secret&amp;nbsp;area&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;I love Braid.  If you&amp;#8217;re not familiar (!), it&amp;#8217;s a platformer where you have the ability to rewind time — whenever you want, for as long as you want, all the way back to when you entered the&amp;nbsp;level.&lt;/p&gt;
&lt;p&gt;The game starts in world 2, where you do fairly standard platforming and use the rewind ability to do some finnicky jumps with minimal frustration.  It gets more interesting in world 3 with the addition of glowing green objects, which aren&amp;#8217;t affected by the reversal of&amp;nbsp;time.&lt;/p&gt;
&lt;p&gt;And then there&amp;#8217;s world 4, &amp;#8220;Time and Place&amp;#8221;.  I &lt;em&gt;love&lt;/em&gt; world 4, so much.  It&amp;#8217;s unlike anything I&amp;#8217;ve ever seen in any other game, and it&amp;#8217;s so simple yet so&amp;nbsp;clever.&lt;/p&gt;
&lt;p&gt;The premise is this: for everything except you, time moves forwards as you move right, and backwards as you move&amp;nbsp;left.&lt;/p&gt;
&lt;p&gt;This has some &lt;em&gt;weird implications&lt;/em&gt;, which all come together in the final level of the world, Fickle Companion.  It&amp;#8217;s so named because you have to use one (single-use) key to open three doors, but that key is very easy to&amp;nbsp;lose.&lt;/p&gt;
&lt;p&gt;Say you pick up the key and walk to the right with it.  Time continues forwards for the key, so it stays with you as expected.  Now you climb a ladder.  Time is frozen since you aren&amp;#8217;t moving horizontally, but the key stays with you anyway.  Now you walk to the left.  Oops — the key follows its own path &lt;em&gt;backwards in time&lt;/em&gt;, going down the ladder and back along the path you carried it in the first place.  You can&amp;#8217;t fix this by walking to the right again, because that will simply advance time normally for the key; since you&amp;#8217;re no longer holding it, it will simply fall to the ground and stay&amp;nbsp;there.&lt;/p&gt;
&lt;p&gt;You can see how this might be a problem in the screenshot above (where you get the key earlier in the level, to the left).  You can climb the first ladder, but to get to the door, you have to walk &lt;em&gt;left&lt;/em&gt; to get to the second ladder, which will reverse the key back down to the&amp;nbsp;ground.&lt;/p&gt;
&lt;p&gt;The solution is in the cannon in the upper right, which spits out a Goomba-like critter.  It has the timeproof green glow, so the critters it spits out have the same green glow — making them immune to both your time reversal power &lt;em&gt;and&lt;/em&gt; to the effect your movement has on time.  What you have to do is get one of the critters to &lt;em&gt;pick up the key&lt;/em&gt; and carry it leftwards for you.  Once you have the puzzle piece, you have to rewind time and do it again elsewhere.  (Or, more likely, the other way around; this next section acts as a decent hint for how to do the earlier&amp;nbsp;section.)&lt;/p&gt;
&lt;div class="prose-full-illustration"&gt;
&lt;img src="https://eev.ee/media/2017-07-01-level-design/braid-fickle-companion-2.png" alt="A puzzle piece trapped behind two doors, in a level containing only one key"&gt;
&lt;/div&gt;

&lt;p&gt;It&amp;#8217;s hard to convey how bizarre this is in just text.  If you haven&amp;#8217;t played Braid, it&amp;#8217;s absolutely worth it just for this one world, this one &lt;em&gt;level&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;And it gets even better, slash more ridiculous: there&amp;#8217;s a super duper secret hidden very cleverly in this level.  Reaching it involves bouncing &lt;em&gt;twice&lt;/em&gt; off of critters; solving the puzzle hidden there involves bouncing the &lt;em&gt;critters&lt;/em&gt; off of &lt;em&gt;you&lt;/em&gt;.  It&amp;#8217;s ludicrous and perhaps a bit too tricky, but very clever.  Best of all, it&amp;#8217;s something that an enterprising player might just think to do on a whim — hey, this is possible here, I wonder what happens if I try it.  And the game &lt;strong&gt;rewards the player for trying something creative&lt;/strong&gt;!  (Ironically, it&amp;#8217;s most rewarding to have a clever idea when it turns out the designer already had the same&amp;nbsp;idea.)&lt;/p&gt;
&lt;p&gt;What can I take away from this?&amp;nbsp;Hm.&lt;/p&gt;
&lt;p&gt;Well, the underlying idea of linking time with position is pretty novel, but getting to it may not be all that hard: just &lt;strong&gt;combine different concepts and see what happens&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;A similar principle is to &lt;strong&gt;apply a general concept to everything and see what happens&lt;/strong&gt;.  This is the first sighting of a timeproof wandering critter; previously timeproofing had only been seen on keys, doors, puzzle pieces, and stationary monsters.  Later it even applies to Tim himself in special&amp;nbsp;circumstances.&lt;/p&gt;
&lt;p&gt;The use of timeproofing on puzzle pieces is especially interesting, because the puzzle pieces — despite being collectibles that animate moving into the &lt;span class="caps"&gt;UI&lt;/span&gt; when you get them — are &lt;em&gt;also&lt;/em&gt; affected by time.  If the pieces in this level weren&amp;#8217;t timeproof, then as soon as you collected one and moved left to leave its alcove, time would move backwards and the puzzle piece would reverse &lt;em&gt;out of the &lt;span class="caps"&gt;UI&lt;/span&gt;&lt;/em&gt; and right back into the&amp;nbsp;world.&lt;/p&gt;
&lt;p&gt;Along similar lines, the music and animated background are also subject to the flow of time.  It&amp;#8217;s obvious enough that the music plays backwards when you rewind time, but in world 4, the music only plays &lt;em&gt;at all&lt;/em&gt; while you&amp;#8217;re moving.  It&amp;#8217;s a fantastic effect that makes the whole world feel as weird and jerky as it really &lt;em&gt;is&lt;/em&gt; under these rules.  It drives the concept home instantly, and it makes your weird influence over time feel all the more significant and far-reaching.  I love when games &lt;strong&gt;weave all the elements of the game into the gameplay&lt;/strong&gt; like this, even (especially?) for the sake of a single oddball&amp;nbsp;level.&lt;/p&gt;
&lt;p&gt;Admittedly, this is all about gameplay or puzzle mechanics, not so much level design.  What I like about the &lt;em&gt;level itself&lt;/em&gt; is how simple and straightforward it is: it contains exactly as much as it needs to, yet still invites trying the wrong thing first, which immediately teaches the player why it won&amp;#8217;t work.  And it&amp;#8217;s something that feels like it &lt;em&gt;ought&lt;/em&gt; to work, except that the rules of the game get in the way just enough.  This makes for my favorite kind of puzzle, the type where you feel like you&amp;#8217;ve tried everything and it &lt;em&gt;must&lt;/em&gt; be impossible — until you realize the creative combination of things you haven&amp;#8217;t tried yet.  I&amp;#8217;m talking about puzzles again, oops; I guess the general level design equivalent of this is that players tend to try the first thing they see first, so if you &lt;strong&gt;put required parts later&lt;/strong&gt;, players will be more likely to see optional&amp;nbsp;parts.&lt;/p&gt;
&lt;p&gt;I think that&amp;#8217;s all I&amp;#8217;ve got for this one puzzle room.  I do want to say (again) that I love &lt;em&gt;both&lt;/em&gt; endings of Braid.  The normal ending weaves together the game mechanics and (admittedly loose) plot in a way that gave me chills when I first saw it; the secret ending &lt;em&gt;completely&lt;/em&gt; changes both how the ending plays and how you might interpret the finale, all by making only the slightest changes to the&amp;nbsp;level.&lt;/p&gt;
&lt;h2 id="portal-testchamber-18-advanced"&gt;&lt;a class="toclink" href="#portal-testchamber-18-advanced"&gt;Portal: Testchamber 18 (advanced)&lt;/a&gt;&lt;/h2&gt;
&lt;div class="prose-full-illustration"&gt;
&lt;img src="https://eev.ee/media/2017-07-01-level-design/portal-testchamber-18-advanced.png" alt="View into a Portal test chamber; the ceiling and most of the walls are covered in metal"&gt;
&lt;/div&gt;

&lt;p&gt;&lt;em&gt;screenshot mine — &lt;a href="https://www.youtube.com/watch?v=ninRkHZ7WOg"&gt;playthrough of normal map&lt;/a&gt; — &lt;a href="https://www.youtube.com/watch?v=wNr9l8EZxz4"&gt;playthrough of advanced&amp;nbsp;map&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;I love Portal.  I blazed through the game in a couple hours the night it came out.  I&amp;#8217;d seen the trailer and &lt;em&gt;instantly&lt;/em&gt; grasped the concept, so the very slow and gentle learning curve was actually a bit frustrating for me; I just wanted to portal around a big playground, and I finally got to do that in the six &amp;#8220;serious&amp;#8221; tests towards the end, 13 through&amp;nbsp;18.&lt;/p&gt;
&lt;p&gt;Valve threw an interesting curveball with these six maps.  As well as being more complete puzzles by themselves, Valve added &amp;#8220;challenges&amp;#8221; requiring that they be done with as few portals, time, or steps as possible.  I only bothered with the portal challenges — time and steps seemed less about puzzle-solving and more about twitchy reflexes — and within them I found buried an extra layer of puzzles.  All of the minimum portal requirements were only possible if you found an alternative solution to the map: skipping part of it, making do with only one cube instead of two, etc.  But Valve offered no hints, only a target number.  It was a clever way to make me think harder about familiar&amp;nbsp;areas.&lt;/p&gt;
&lt;p&gt;Alongside the challenges were &amp;#8220;advanced&amp;#8221; maps, and &lt;em&gt;these&lt;/em&gt; blew me away.  They were six maps identical in layout to the last six test chambers, but with a simple added twist that completely changed how you had to approach them.  Test 13 has two buttons with two boxes to place on them; the advanced version removes a box and also changes the floor to lava.  Test 14 is a live fire course with turrets you have to knock over; the advanced version puts them all in impenetrable cages.  Test 17 is based around making extensive use of a single cube; the advanced version changes it to a&amp;nbsp;ball.&lt;/p&gt;
&lt;p&gt;But the one that sticks out the most to me is test 18, a potpourri of everything you&amp;#8217;ve learned so far.  The beginning part has you cross several large pits of toxic sludge by portaling from the ceilings; the advanced version simply changes the ceilings to unportalable metal.  It seems you&amp;#8217;re completely stuck after only the first jump, unless you happen to catch a glimpse of the portalable &lt;em&gt;floor&lt;/em&gt; you pass over in mid-flight.  Or you might remember from the regular version of the map that the floor was portalable there, since you used it to progress further.  Either way, you have to fire a portal in midair in a way you&amp;#8217;ve never had to do before, and the result feels &lt;em&gt;very cool&lt;/em&gt;, like you&amp;#8217;ve defeated a puzzle that was intended to be unsolvable.  All in a level that was fairly easy the first time around, and has been modified only&amp;nbsp;slightly.&lt;/p&gt;
&lt;p&gt;I&amp;#8217;m not sure where I&amp;#8217;m going with this.  I could say it&amp;#8217;s good to &lt;strong&gt;make the player feel clever&lt;/strong&gt;, but that feels wishy-washy.  What I really appreciated about the advanced tests is that they exploited inklings of ideas I&amp;#8217;d started to have when playing through the regular game; they encouraged me to take the spark of inspiration this game mechanic gave me and &lt;em&gt;run with it&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;So I suppose the better underlying principle here — the &lt;em&gt;most&lt;/em&gt; important principle in level design, in any creative work — is to &lt;strong&gt;latch onto what gets you fired up and run with it&lt;/strong&gt;.  I am absolutely certain that the level designers for this game loved the portal concept as much as I do, they explored it thoroughly, and they felt &lt;em&gt;compelled&lt;/em&gt; to fit their wilder puzzle ideas in&amp;nbsp;somehow.&lt;/p&gt;
&lt;p&gt;More of that.  Find the stuff that feels like it&amp;#8217;s going to burst out of your head, and let it&amp;nbsp;burst.&lt;/p&gt;
&lt;h2 id="chips-challenge-level-122-totally-fair-and-level-131-totally-unfair"&gt;&lt;a class="toclink" href="#chips-challenge-level-122-totally-fair-and-level-131-totally-unfair"&gt;Chip's Challenge: Level 122, Totally Fair and Level 131, Totally Unfair&lt;/a&gt;&lt;/h2&gt;
&lt;div class="prose-full-illustration"&gt;
&lt;img src="https://eev.ee/media/2017-07-01-level-design/chips-challenge-totally-fair-mechanism.png" alt="A small maze containing a couple monsters and ending at a brown button"&gt;
&lt;/div&gt;

&lt;p&gt;&lt;em&gt;screenshots mine — &lt;a href="https://strategywiki.org/wiki/Chip%27s_Challenge/Levels_121-140"&gt;full maps of both levels&lt;/a&gt; — &lt;a href="https://www.youtube.com/watch?v=ZOdjE8KhaXc"&gt;playthrough of Totally Fair&lt;/a&gt; — &lt;a href="https://www.youtube.com/watch?v=zn8wR53CxuA"&gt;playthrough of Totally&amp;nbsp;Unfair&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;I mention this because Portal reminded me of it.  The regular and advanced maps in Portal are reminiscent of parallel worlds or duality or whatever you want to call the theme.  I extremely dig that theme, and it shows up in Chip&amp;#8217;s Challenge in an unexpected&amp;nbsp;way.&lt;/p&gt;
&lt;p&gt;Totally Fair is a wide open level with a little maze walled off in one corner.  The maze contains a monster called a &amp;#8220;teeth&amp;#8221;, which follows Chip at a slightly slower speed.  (The second teeth, here shown facing upwards, starts outside the maze but followed me into it when I took this&amp;nbsp;screenshot.)&lt;/p&gt;
&lt;p&gt;The goal is to lure the teeth into standing on the brown button on the right side.  If anything moves into a &amp;#8220;trap&amp;#8221; tile (the larger brown recesses at the bottom), it cannot move out of that tile until/unless something steps on the corresponding brown button.  So there&amp;#8217;s not much room for error in maneuvering the teeth; if it falls in the water up top, it&amp;#8217;ll die, and if it touches the traps at the bottom, it&amp;#8217;ll be stuck&amp;nbsp;permanently.&lt;/p&gt;
&lt;p&gt;The &lt;em&gt;reason&lt;/em&gt; you need the brown button pressed is to acquire the chips on the far right edge of the&amp;nbsp;level.&lt;/p&gt;
&lt;div class="prose-full-illustration"&gt;
&lt;img src="https://eev.ee/media/2017-07-01-level-design/chips-challenge-totally-unfair-chips.png" alt="Several chips that cannot be obtained without stepping on a trap"&gt;
&lt;/div&gt;

&lt;p&gt;The gray recesses turn into walls after being stepped on, so once you grab a chip, the only way out is through the force floors and ice that will send you onto the trap.  If you haven&amp;#8217;t maneuvered the teeth onto the button beforehand, you&amp;#8217;ll be trapped&amp;nbsp;there.&lt;/p&gt;
&lt;p&gt;Doesn&amp;#8217;t seem like a huge deal, since you can go see exactly how the maze is shaped and move the teeth into position fairly easily.  But you see, here is the beginning of Totally&amp;nbsp;Fair.&lt;/p&gt;
&lt;div class="prose-full-illustration"&gt;
&lt;img src="https://eev.ee/media/2017-07-01-level-design/chips-challenge-totally-fair-entrance.png" alt="A wall with a single recessed gray space in it"&gt;
&lt;/div&gt;

&lt;p&gt;The gray recess leads up into the maze area, so you can only enter it once.  A force floor in the upper right lets you exit&amp;nbsp;it.&lt;/p&gt;
&lt;p&gt;Totally Unfair is exactly identical, except the second teeth has been removed, and the entrance to the maze looks like&amp;nbsp;this.&lt;/p&gt;
&lt;div class="prose-full-illustration"&gt;
&lt;img src="https://eev.ee/media/2017-07-01-level-design/chips-challenge-totally-unfair-entrance.png" alt="The same wall is now completely solid, and the recess has been replaced with a hint"&gt;
&lt;/div&gt;

&lt;p&gt;You can&amp;#8217;t get into the maze area.  You can&amp;#8217;t even see the maze; it&amp;#8217;s too far away from the wall.  You have to position the teeth completely blind.  In fact, if you take a single step to the left from here, you&amp;#8217;ll have already dumped the teeth into the water and rendered the level&amp;nbsp;impossible.&lt;/p&gt;
&lt;p&gt;The hint tile will tell you to &amp;#8220;Remember sjum&amp;#8221;,&amp;nbsp;where &lt;code&gt;SJUM&lt;/code&gt; is the password to get back to Totally Fair.  So you have to learn that level well enough to recreate the same effect without being able to see your&amp;nbsp;progress.&lt;/p&gt;
&lt;p&gt;It&amp;#8217;s not impossible, and it&amp;#8217;s not a &amp;#8220;make a map&amp;#8221; faux puzzle.  A few scattered wall blocks near the chips, &lt;em&gt;outside&lt;/em&gt; the maze area, are arranged exactly where the edges of the maze are.  Once you notice that, all you have to do is walk up and down a few times, waiting a moment each time to make sure the teeth has caught up with&amp;nbsp;you.&lt;/p&gt;
&lt;p&gt;So in a sense, Totally Unfair is the advanced chamber version of Totally Fair.  It makes a very minor change that force you to approach the whole level completely differently, using knowledge gleaned from your first&amp;nbsp;attempt.&lt;/p&gt;
&lt;p&gt;And crucially, it&amp;#8217;s an actual &lt;em&gt;puzzle&lt;/em&gt;!  A lot of later Chip&amp;#8217;s Challenge levels rely heavily on map-drawing, timing, tedium, or outright luck.  (Consider, if you will, &lt;a href="https://strategywiki.org/wiki/Chip%27s_Challenge/Levels_121-140#Level_133:_BLOBDANCE"&gt;Blobdance&lt;/a&gt;.)  The Totally Fair + Totally Unfair pairing requires a little ingenuity unlike anything else in the game, and the solution is something more than just combinations of existing game mechanics.  There&amp;#8217;s something very interesting about that hint in the walls, a hint you&amp;#8217;d have no reason to pick up on when playing through the first level.  I wish I knew how to verbalize it&amp;nbsp;better.&lt;/p&gt;
&lt;p&gt;Anyway, enough puzzle games; let&amp;#8217;s get back to regular ol&amp;#8217; level&amp;nbsp;design.&lt;/p&gt;
&lt;h2 id="links-awakening-level-7-eagles-tower"&gt;&lt;a class="toclink" href="#links-awakening-level-7-eagles-tower"&gt;Link's Awakening: Level 7, Eagle's Tower&lt;/a&gt;&lt;/h2&gt;
&lt;div class="prose-full-illustration"&gt;
&lt;img src="https://eev.ee/media/2017-07-01-level-design/links-awakening-eagle-tower-before.png" alt="A 4×4 arrangement of rooms with a conspicuous void in the middle"&gt;
&lt;/div&gt;

&lt;p&gt;&lt;em&gt;maps via &lt;a href="http://www.vgmaps.com/Atlas/GB-GBC/index.htm#LegendOfZeldaLinksAwakeningDX"&gt;vgmaps&lt;/a&gt; and &lt;a href="https://tcrf.net/The_Legend_of_Zelda:_Link%27s_Awakening#Eagle.27s_Tower"&gt;&lt;span class="caps"&gt;TCRF&lt;/span&gt;&lt;/a&gt; — &lt;a href="https://www.youtube.com/watch?v=ffJ5WGob0F8"&gt;playthrough with&amp;nbsp;commentary&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;Link&amp;#8217;s Awakening was my first Zelda (and only Zelda for a long time), which made for a slightly confusing introduction to the series — what on earth is a Zelda and why doesn&amp;#8217;t it appear in the&amp;nbsp;game?&lt;/p&gt;
&lt;p&gt;The whole game is a blur of curiosities and interesting little special cases.  It&amp;#8217;s fabulously well put together, especially for a Game Boy game, and the dungeons in particular are fascinating microcosms of design.  I never really appreciated it before, but looking at the &lt;a href="http://www.vgmaps.com/Atlas/GB-GBC/index.htm#LegendOfZeldaLinksAwakeningDX"&gt;full maps&lt;/a&gt;, I&amp;#8217;m struck by how each dungeon has several large areas neatly sliced into individual&amp;nbsp;screens.&lt;/p&gt;
&lt;p&gt;Much like with Doom &lt;span class="caps"&gt;II&lt;/span&gt;, I surprise myself by picking Eagle&amp;#8217;s Tower as the most notable part of the game.  The dungeon isn&amp;#8217;t that interesting within the overall context of the game; it gives you only the mirror shield, possibly the least interesting item in the game, second only to the power bracelet upgrade from the previous dungeon.  The dungeon itself is fairly long, full of traps, and overflowing with crystal switches and toggle blocks, making it possibly the most frustrating of the set.  Getting to it involves spending some excellent quality time with a flying rooster, but you don&amp;#8217;t really &lt;em&gt;do&lt;/em&gt; anything — mostly you just make your way through nondescript caves and&amp;nbsp;mountaintops.&lt;/p&gt;
&lt;p&gt;Having now thoroughly dunked on it, I&amp;#8217;ll tell you what makes it stand out: &lt;em&gt;the player changes the shape of the dungeon&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;That&amp;#8217;s something I like a lot about Doom, as well, but it&amp;#8217;s much more dramatic in Eagle&amp;#8217;s Tower.  As you might expect, the dungeon is shaped like a tower, where each floor is on a 4×4 grid.  The top floor, 4F, is a small 2×2 block of rooms in the middle — but one of those rooms is the boss door, and there&amp;#8217;s no way to get to that&amp;nbsp;floor.&lt;/p&gt;
&lt;p&gt;(Well, sort of.  The &amp;#8220;down&amp;#8221; stairs in the upper-right of 3F actually lead up to 4F, but the connection is bogus and puts you in a wall, and both of the upper middle rooms are unreachable during normal&amp;nbsp;gameplay.)&lt;/p&gt;
&lt;p&gt;The primary objective of the dungeon is to smash four support columns on 2F by throwing a huge iron ball at them, which causes 4F to &lt;em&gt;crash down&lt;/em&gt; into the middle of&amp;nbsp;3F.&lt;/p&gt;
&lt;div class="prose-full-illustration"&gt;
&lt;img src="https://eev.ee/media/2017-07-01-level-design/links-awakening-eagle-tower-after.png" alt="The same arrangement of rooms, but the four in the middle have changed"&gt;
&lt;/div&gt;

&lt;p&gt;Even the map on the pause screen updates to reflect this.  In every meaningful sense, &lt;em&gt;you&lt;/em&gt;, the player, have fundamentally reconfigured the shape of this&amp;nbsp;dungeon.&lt;/p&gt;
&lt;p&gt;I &lt;em&gt;love&lt;/em&gt; this.  It feels like I have some impact on the world, that I came along and did something much more significant than mere game mechanics ought to allow.  I saw that the tower was unsolvable as designed, so &lt;em&gt;I fixed it&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;It&amp;#8217;s clear that the game engine supports rearranging screens arbitrarily — consider the Wind Fish&amp;#8217;s Egg — but this is s wonderfully clever and subtle use of that.  &lt;strong&gt;Let the player feel like they have an impact on the&amp;nbsp;world.&lt;/strong&gt;&lt;/p&gt;
&lt;h2 id="the-cutting-room-floor"&gt;&lt;a class="toclink" href="#the-cutting-room-floor"&gt;The cutting room floor&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;This is getting excessively long so I&amp;#8217;m gonna cut it here.  Some other things I thought of but don&amp;#8217;t know how to say more than a paragraph&amp;nbsp;about:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Super Mario Land 2: Six Golden Coins has a lot of levels with completely unique themes, backed by very simple tilesets but enhanced by interesting one-off obstacles and enemies.  I don&amp;#8217;t even know how to pick a most interesting one.  Maybe just play the game, or at least &lt;a href="http://www.vgmaps.com/Atlas/GB-GBC/index.htm#SuperMarioLand26GoldenCoins"&gt;peruse the maps&lt;/a&gt;.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href="http://www.nodraw.net/2010/08/tf2-density-of-detailing/"&gt;This post about density of detail in Team Fortress 2&lt;/a&gt; is really good so just read that I guess.  It&amp;#8217;s really about careful balance of contrast again, but through the lens of using contrasting amounts of detail to draw the player&amp;#8217;s attention, while still carrying a simple theme through less detailed&amp;nbsp;areas.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Metroid Prime is pretty interesting in a lot of ways, but I mostly laugh at how they spaced rooms out with long twisty hallways to improve load times — yet I never really thought about it because they all feel like they belong in the&amp;nbsp;game.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;One thing I really &lt;em&gt;appreciate&lt;/em&gt; is level design that hints at a story, that shows me a world that exists persistently, that convinces me this space exists for some reason other than as a gauntlet for me as a player.  But it seems what comes first to my mind is level design that&amp;#8217;s clever or quirky, which probably says a lot about me.  Maybe the original Fallouts are a good place to look for that sort of&amp;nbsp;detail.&lt;/p&gt;
&lt;p&gt;Conversely, it sticks out like a sore thumb when a game tries to railroad me into experiencing the game As The Designer Intended.  Games are interactive, so the more input the player can give, the better — and this can be as simple as deciding to avoid rather than confront enemies, or deciding to run rather than&amp;nbsp;walk.&lt;/p&gt;
&lt;p&gt;I think that&amp;#8217;s all I&amp;#8217;ve got in me at the moment.  Clearly I need to meditate on this a lot more, but I hope some of this was inspiring in some&amp;nbsp;way!&lt;/p&gt;</content><category term="articles"></category><category term="tech"></category><category term="patreon"></category></entry><entry><title>Digital painter rundown</title><link href="https://eev.ee/blog/2017/06/17/digital-painter-rundown/" rel="alternate"></link><published>2017-06-17T00:58:00-07:00</published><updated>2017-06-17T00:58:00-07:00</updated><author><name>Eevee</name></author><id>tag:eev.ee,2017-06-17:/blog/2017/06/17/digital-painter-rundown/</id><summary type="html">&lt;p&gt;Another &lt;a href="https://www.patreon.com/eevee"&gt;patron&lt;/a&gt; post!  &lt;a href="https://www.patreon.com/user?u=199476"&gt;IndustrialRobot&lt;/a&gt; asks:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;You should totally write about drawing/image manipulation programs! (Inspired by &lt;a href="https://eev.ee/blog/2015/05/31/text-editor-rundown/"&gt;https://eev.ee/blog/2015/05/31/text-editor-rundown/&lt;/a&gt;)&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;This is a little trickier than a text editor comparison — while most text editors are cross-platform, quite a few digital art programs are &lt;em&gt;not&lt;/em&gt;.  So I’m effectively unable to even try a decent chunk of the offerings.  I’m also still a &lt;em&gt;relatively&lt;/em&gt; new artist, and image editors are much harder to briefly compare than text editors…&lt;/p&gt;
&lt;p&gt;Right, now that your expectations have been suitably lowered:&lt;/p&gt;
</summary><content type="html">&lt;p&gt;Another &lt;a href="https://www.patreon.com/eevee"&gt;patron&lt;/a&gt; post!  &lt;a href="https://www.patreon.com/user?u=199476"&gt;IndustrialRobot&lt;/a&gt;&amp;nbsp;asks:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;You should totally write about drawing/image manipulation programs! (Inspired by &lt;a href="https://eev.ee/blog/2015/05/31/text-editor-rundown/"&gt;https://eev.ee/blog/2015/05/31/text-editor-rundown/&lt;/a&gt;)&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;This is a little trickier than a text editor comparison — while most text editors are cross-platform, quite a few digital art programs are &lt;em&gt;not&lt;/em&gt;.  So I&amp;#8217;m effectively unable to even try a decent chunk of the offerings.  I&amp;#8217;m also still a &lt;em&gt;relatively&lt;/em&gt; new artist, and image editors are much harder to briefly compare than text&amp;nbsp;editors&amp;#8230;&lt;/p&gt;
&lt;p&gt;Right, now that your expectations have been suitably&amp;nbsp;lowered:&lt;/p&gt;


&lt;h2 id="krita"&gt;&lt;a class="toclink" href="#krita"&gt;Krita&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;I do all of my digital art in &lt;a href="https://krita.org/en/"&gt;Krita&lt;/a&gt;.  It&amp;#8217;s pretty&amp;nbsp;alright.&lt;/p&gt;
&lt;p&gt;&amp;#8230;&lt;/p&gt;
&lt;p&gt;Okay so Krita grew out of &lt;a href="https://www.calligra.org/"&gt;Calligra&lt;/a&gt;, which used to be KOffice, which was an office suite designed for &lt;span class="caps"&gt;KDE&lt;/span&gt; (a Linux desktop environment).  I bring this up because &lt;span class="caps"&gt;KDE&lt;/span&gt; has a certain&amp;#8230;  reputation.  With &lt;span class="caps"&gt;KDE&lt;/span&gt;, there are at least three completely different ways to do &lt;em&gt;anything&lt;/em&gt;, each of those ways has ludicrous amounts of customization and settings, and somehow it still can&amp;#8217;t do what you&amp;nbsp;want.&lt;/p&gt;
&lt;p&gt;Krita inherits this aesthetic by attempting to do &lt;em&gt;literally everything&lt;/em&gt;.  It has 17 different brush engines, more than 70 layer blending modes, seven color picker dockers, and an ungodly number of colorspaces.  It&amp;#8217;s clearly intended primarily for drawing, but it also supports animation and vector layers and a pretty decent spread of raster editing tools.  I &lt;em&gt;just right now&lt;/em&gt; discovered that it has Photoshop-like &amp;#8220;layer styles&amp;#8221; (e.g. drop shadow), after a year and a half of using&amp;nbsp;it.&lt;/p&gt;
&lt;p&gt;In fairness, Krita manages all of this stuff well enough, and (apparently!) it manages to stay out of your way if you&amp;#8217;re not using it.  In less fairness, they managed to break erasing with a Wacom tablet pen for three&amp;nbsp;months?&lt;/p&gt;
&lt;p&gt;I don&amp;#8217;t want to rag on it too hard; it&amp;#8217;s an impressive piece of work, and I enjoy using it!  The emotion it evokes isn&amp;#8217;t so much frustration as&amp;#8230;  mystified&amp;nbsp;bewilderment.&lt;/p&gt;
&lt;p&gt;I once filed a ticket suggesting the addition of a brush size palette — a panel showing a grid of fixed brush sizes that makes it easy to switch between known sizes with a tablet pen (and increases the chances that you&amp;#8217;ll be able to get a brush back to the right size again).  It&amp;#8217;s a prominent feature of Paint Tool &lt;span class="caps"&gt;SAI&lt;/span&gt; and Clip Studio Paint, and while I&amp;#8217;ve never used either of those myself, I&amp;#8217;ve seen a good few artists swear by&amp;nbsp;it.&lt;/p&gt;
&lt;p&gt;The developer response was that I could emulate the behavior by creating brush presets.  But that&amp;#8217;s flat-out wrong: getting the same effect would require creating a ton of brush presets &lt;em&gt;for every brush I have&lt;/em&gt;, plus giving them all distinct icons so the size is obvious at a glance.  Even then, it would be much more tedious to use and fill my presets with&amp;nbsp;junk.&lt;/p&gt;
&lt;p&gt;And that sort of response is what&amp;#8217;s so mysterious to me.  I&amp;#8217;ve never even been able to use this feature myself, but a year of amateur painting with Krita has convinced me that it would be pretty useful.  But a developer didn&amp;#8217;t see the use &lt;em&gt;and&lt;/em&gt; suggested an incredibly tedious alternative that only half-solves the problem and creates new ones.  Meanwhile, of the 28 existing dockable panels, &lt;em&gt;a quarter&lt;/em&gt; of them are different ways to choose&amp;nbsp;colors.&lt;/p&gt;
&lt;p&gt;What is Krita trying to be, then?  What does Krita think it is?  Who precisely is the target audience?  I have no&amp;nbsp;idea.&lt;/p&gt;
&lt;hr /&gt;
&lt;p&gt;Anyway, I enjoy drawing in Krita well enough.  It ships with a respectable set of brushes, and there are &lt;a href="https://docs.krita.org/Resources"&gt;plenty more&lt;/a&gt; floating around.  It has canvas rotation, canvas mirroring, perspective guide tools, and other art goodies.  It doesn&amp;#8217;t colordrop on right click by default, which is arguably a grave sin (it shows a customizable radial menu instead), but that&amp;#8217;s easy to rebind.  It understands having a background color &lt;em&gt;beneath&lt;/em&gt; a bottom transparent layer, which is very nice.  You can also toggle &lt;em&gt;any&lt;/em&gt; brush between painting and erasing with the press of a button, and that turns out to be very&amp;nbsp;useful.&lt;/p&gt;
&lt;p&gt;It doesn&amp;#8217;t support infinite canvases, though it does offer a one-click button to extend the canvas in a given direction.  I&amp;#8217;ve never used it (and didn&amp;#8217;t even know what it did until just now), but would totally use an infinite&amp;nbsp;canvas.&lt;/p&gt;
&lt;p&gt;I haven&amp;#8217;t used the animation support too much, but it&amp;#8217;s pretty nice to have.  Granted, the only other animation software I&amp;#8217;ve used is Aseprite, so I don&amp;#8217;t have many points of reference here.  It&amp;#8217;s a relatively new addition, too, so I assume it&amp;#8217;ll improve over&amp;nbsp;time.&lt;/p&gt;
&lt;p&gt;The one annoyance I remember with animation was really an interaction with a larger annoyance, which is: working with selections kind of sucks.  You can&amp;#8217;t drag a selection around with the selection tool; you have to switch to the move tool.  That would be fine if you could at least drag the selection &lt;em&gt;ring&lt;/em&gt; around with the selection tool, but you can&amp;#8217;t do that either; dragging just creates a new&amp;nbsp;selection.&lt;/p&gt;
&lt;p&gt;If you want to &lt;em&gt;copy&lt;/em&gt; a selection, you have to explicitly copy it to the clipboard and paste it, which &lt;em&gt;creates a new layer&lt;/em&gt;.  Ctrl-drag with the move tool doesn&amp;#8217;t work.  So then you have to merge that layer down, which I think is where the problem with animation comes in: a new layer is non-animated by default, meaning it effectively appears in any frame, so simply merging it down with merge it onto &lt;em&gt;every single frame&lt;/em&gt; of the layer below.  And you won&amp;#8217;t even notice until you switch frames or play back the animation.  Not&amp;nbsp;ideal.&lt;/p&gt;
&lt;p&gt;This is another thing that makes me wonder about Krita&amp;#8217;s sense of identity.  It has a lot of fancy general-purpose raster editing features that even &lt;span class="caps"&gt;GIMP&lt;/span&gt; is still struggling to implement, like high color depth support and non-destructive filters, yet something as basic as working with selections is clumsy.  (In fairness, &lt;span class="caps"&gt;GIMP&lt;/span&gt; is a bit clumsy here too, but it has a consistent notion of &amp;#8220;floating selection&amp;#8221; that&amp;#8217;s easy enough to work&amp;nbsp;with.)&lt;/p&gt;
&lt;p&gt;I don&amp;#8217;t know how well Krita would work &lt;em&gt;as&lt;/em&gt; a general-purpose raster editor; I&amp;#8217;ve never tried to use it that way.  I can&amp;#8217;t think of anything obvious that&amp;#8217;s missing.  The only real gotcha is that some things you might expect to be tools, like smudge or clone, are just types of brush in&amp;nbsp;Krita.&lt;/p&gt;
&lt;h2 id="gimp"&gt;&lt;a class="toclink" href="#gimp"&gt;GIMP&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Ah, &lt;a href="https://www.gimp.org/"&gt;&lt;span class="caps"&gt;GIMP&lt;/span&gt;&lt;/a&gt; — open source&amp;#8217;s answer to&amp;nbsp;Photoshop.&lt;/p&gt;
&lt;p&gt;It&amp;#8217;s very obviously intended for raster editing, and I&amp;#8217;m pretty familiar with it after half a lifetime of only using Linux.  I even wrote a little Scheme script for it &lt;em&gt;ages&lt;/em&gt; ago to automate some simple edits to a couple hundred files, back before I was aware of ImageMagick.  I don&amp;#8217;t know what to say about it, specifically; it&amp;#8217;s fairly powerful and does a wide variety of&amp;nbsp;things.&lt;/p&gt;
&lt;p&gt;In fact I&amp;#8217;d say it&amp;#8217;s almost &lt;em&gt;frustratingly&lt;/em&gt; intended for raster editing.  I used &lt;span class="caps"&gt;GIMP&lt;/span&gt; in my first attempts at digital painting, before I&amp;#8217;d heard of Krita.  It was &lt;em&gt;okay&lt;/em&gt;, but so much of it felt clunky and awkward.  Painting is split between a pencil tool, a paintbrush tool, and an airbrush tool; I don&amp;#8217;t really know why.  The default brushes are largely uninteresting.  Instead of brush presets, there are &lt;em&gt;tool&lt;/em&gt; presets that can be saved for any tool; it&amp;#8217;s a neat idea, but doesn&amp;#8217;t feel like a real substitute for brush&amp;nbsp;presets.&lt;/p&gt;
&lt;p&gt;Much of the same functionality as Krita is there, but it&amp;#8217;s all somehow more clunky.  I&amp;#8217;m sure it&amp;#8217;s possible to fiddle with the interface to get something friendlier for painting, but I never really figured out&amp;nbsp;how.&lt;/p&gt;
&lt;p&gt;And then there&amp;#8217;s the surprising stuff that&amp;#8217;s &lt;em&gt;missing&lt;/em&gt;.  There&amp;#8217;s no canvas rotation, for example.  There&amp;#8217;s only one type of brush, and it just stamps the same pattern along a path.  I don&amp;#8217;t think it&amp;#8217;s possible to smear or blend or pick up color while painting.  The only way to change the brush size is via the very sensitive slider on the tool options panel, which I remember being a little annoying with a tablet pen.  Also, you have to specifically &lt;em&gt;enable&lt;/em&gt; tablet support?  It&amp;#8217;s not difficult or anything, but I have no idea why the default is to &lt;em&gt;ignore&lt;/em&gt; tablet pressure and treat it like a regular mouse&amp;nbsp;cursor.&lt;/p&gt;
&lt;p&gt;As I mentioned above, there&amp;#8217;s also no support for high color depth or non-destructive editing, which is honestly a little embarrassing.  Those are the major things Serious Professionals™ have been asking for for ages, and &lt;span class="caps"&gt;GIMP&lt;/span&gt; has been trying to provide them, but it&amp;#8217;s taking a very long time.  The first signs of &lt;span class="caps"&gt;GEGL&lt;/span&gt;, a new library intended to provide these features, appeared in &lt;span class="caps"&gt;GIMP&lt;/span&gt; 2.6&amp;#8230;  in 2008.  The last major release was in 2012.  &lt;span class="caps"&gt;GIMP&lt;/span&gt; has been working on this new plumbing for almost as long as Krita&amp;#8217;s &lt;em&gt;entire development history&lt;/em&gt;.  (To be fair, Krita has also raised almost €90,000 from three Kickstarters to fund its development; I don&amp;#8217;t know that &lt;span class="caps"&gt;GIMP&lt;/span&gt; is funded at&amp;nbsp;all.)&lt;/p&gt;
&lt;p&gt;I don&amp;#8217;t know what&amp;#8217;s up with &lt;span class="caps"&gt;GIMP&lt;/span&gt; nowadays.  It&amp;#8217;s still under active development, but the exact status and roadmap are a little unclear.  I still use it for some general-purpose editing, but I don&amp;#8217;t see any reason to use it to&amp;nbsp;draw.&lt;/p&gt;
&lt;p&gt;I do know that canvas rotation will be in the next release, and there was some experimentation with embedding MyPaint&amp;#8217;s brush engine (though when I tried it it was basically unusable), so maybe &lt;span class="caps"&gt;GIMP&lt;/span&gt; &lt;em&gt;is&lt;/em&gt; interested in wooing artists?  I guess we&amp;#8217;ll&amp;nbsp;see.&lt;/p&gt;
&lt;h2 id="mypaint"&gt;&lt;a class="toclink" href="#mypaint"&gt;MyPaint&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Ah, &lt;a href="http://mypaint.org/"&gt;MyPaint&lt;/a&gt;.  I gave it a try once.&amp;nbsp;Once.&lt;/p&gt;
&lt;p&gt;It&amp;#8217;s a shame, really.  It &lt;em&gt;sounds&lt;/em&gt; pretty great: specifically built for drawing, has very powerful brushes, supports an infinite canvas, supports canvas rotation, has a simple &lt;span class="caps"&gt;UI&lt;/span&gt; that gets out of your way.&amp;nbsp;Perfect.&lt;/p&gt;
&lt;p&gt;Or so it seems.  But in MyPaint&amp;#8217;s eagerness to shed unnecessary raster editing tools, it forgot a few of the more useful ones.  Like&amp;nbsp;selections.&lt;/p&gt;
&lt;p&gt;MyPaint has no notion of a selection, nor of copy/paste.  If you want to move a head to align better to a body, for example, the sanctioned approach is to duplicate the layer, erase the head from the old layer, erase everything &lt;em&gt;but&lt;/em&gt; the head from the new layer, then move the new&amp;nbsp;layer.&lt;/p&gt;
&lt;p&gt;I can&amp;#8217;t find anything that resembles &lt;span class="caps"&gt;HSL&lt;/span&gt; adjustment, either.  I guess the workaround for that is to create H/S/L layers and floodfill them with different colors until you get what you&amp;nbsp;want.&lt;/p&gt;
&lt;p&gt;I can&amp;#8217;t work seriously without these basic editing tools.  I could see myself doodling in MyPaint, but Krita works just as well for doodling as for serious painting, so I&amp;#8217;ve never gone back to&amp;nbsp;it.&lt;/p&gt;
&lt;h2 id="drawpile"&gt;&lt;a class="toclink" href="#drawpile"&gt;Drawpile&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;&lt;a href="https://drawpile.net/"&gt;Drawpile&lt;/a&gt; is the modern equivalent to OpenCanvas, I suppose?  It lets multiple people draw on the same canvas simultaneously.  (I would not recommend it as a general-purpose raster&amp;nbsp;editor.)&lt;/p&gt;
&lt;p&gt;It&amp;#8217;s a &lt;em&gt;little&lt;/em&gt; clunky in places — I sometimes have bugs where keyboard focus gets stuck in the chat, or my tablet cursor becomes invisible — but the collaborative part works surprisingly well.  It&amp;#8217;s not a brush powerhouse or anything, and I don&amp;#8217;t think it allows textured brushes, but it supports tablet pressure and canvas rotation and locked alpha and &lt;em&gt;selections&lt;/em&gt; and&amp;nbsp;whatnot.&lt;/p&gt;
&lt;p&gt;I&amp;#8217;ve used it a couple times, and it&amp;#8217;s worked well enough that&amp;#8230;  well, other people made pretty decent drawings with it?  I&amp;#8217;m not sure I&amp;#8217;ve managed yet.  And I wouldn&amp;#8217;t use it single-player.  Still, it&amp;#8217;s&amp;nbsp;fun.&lt;/p&gt;
&lt;h2 id="aseprite"&gt;&lt;a class="toclink" href="#aseprite"&gt;Aseprite&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;&lt;a href="https://www.aseprite.org/"&gt;Aseprite&lt;/a&gt; is for pixel art so it doesn&amp;#8217;t really belong here at all.  But it&amp;#8217;s very good at that and I like it a&amp;nbsp;lot.&lt;/p&gt;
&lt;h2 id="thats-all"&gt;&lt;a class="toclink" href="#thats-all"&gt;That's all&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;I can&amp;#8217;t name any other serious contender that exists for&amp;nbsp;Linux.&lt;/p&gt;
&lt;p&gt;I&amp;#8217;m dimly aware of a thing called &amp;#8220;Photo Shop&amp;#8221; that&amp;#8217;s more intended for photos but functions as a passable painter.  More artists seem to swear by Paint Tool &lt;span class="caps"&gt;SAI&lt;/span&gt; and Clip Studio Paint.  Also there&amp;#8217;s Paint.&lt;span class="caps"&gt;NET&lt;/span&gt;, but I have no idea how well it&amp;#8217;s actually suited for&amp;nbsp;painting.&lt;/p&gt;
&lt;p&gt;And that&amp;#8217;s it!  That&amp;#8217;s all I&amp;#8217;ve got.  Krita for drawing, &lt;span class="caps"&gt;GIMP&lt;/span&gt; for editing, Drawpile for collaborative&amp;nbsp;doodling.&lt;/p&gt;</content><category term="articles"></category><category term="tech"></category><category term="patreon"></category></entry><entry><title>Teaching tech</title><link href="https://eev.ee/blog/2017/06/10/teaching-tech/" rel="alternate"></link><published>2017-06-10T15:16:00-07:00</published><updated>2017-06-10T15:16:00-07:00</updated><author><name>Eevee</name></author><id>tag:eev.ee,2017-06-10:/blog/2017/06/10/teaching-tech/</id><summary type="html">&lt;p&gt;A &lt;a href="https://www.patreon.com/eevee"&gt;sponsored post&lt;/a&gt; from an anonymous patron:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;I would kinda like to hear about any thoughts you have on technical teaching or technical writing. Pedagogy is something I care about. But I don’t know how much you do, so feel free to ignore this suggestion :)&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Good news: I care enough that I’m trying to write a sorta-kinda-teaching book!&lt;/p&gt;
&lt;p&gt;Ironically, one of the biggest problems I’ve had with writing the introduction to that book is that I keep accidentally rambling on for pages about problems and difficulties with teaching technical subjects.  So maybe this is a good chance to get it out of my system.&lt;/p&gt;
</summary><content type="html">&lt;p&gt;A &lt;a href="https://www.patreon.com/eevee"&gt;sponsored post&lt;/a&gt; from an anonymous&amp;nbsp;patron:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;I would kinda like to hear about any thoughts you have on technical teaching or technical writing. Pedagogy is something I care about. But I don&amp;#8217;t know how much you do, so feel free to ignore this suggestion&amp;nbsp;:)&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Good news: I care enough that I&amp;#8217;m trying to write a sorta-kinda-teaching&amp;nbsp;book!&lt;/p&gt;
&lt;p&gt;Ironically, one of the biggest problems I&amp;#8217;ve had with writing the introduction to that book is that I keep accidentally rambling on for pages about problems and difficulties with teaching technical subjects.  So maybe this is a good chance to get it out of my&amp;nbsp;system.&lt;/p&gt;


&lt;h2 id="phaser"&gt;&lt;a class="toclink" href="#phaser"&gt;Phaser&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;I recently tried out a new thing.  It was &lt;a href="https://phaser.io/"&gt;Phaser&lt;/a&gt;, but this isn&amp;#8217;t a dig on them in particular, just a convenient example fresh in my mind.  If anything, they&amp;#8217;re better than&amp;nbsp;most.&lt;/p&gt;
&lt;p&gt;As you can see from Phaser&amp;#8217;s website, it appears to have &lt;em&gt;tons&lt;/em&gt; of documentation.    Two of the six headings are &amp;#8220;&lt;span class="caps"&gt;LEARN&lt;/span&gt;&amp;#8221; and &amp;#8220;&lt;span class="caps"&gt;EXAMPLES&lt;/span&gt;&amp;#8221;, which seems very promising.  And indeed, Phaser&amp;nbsp;offers:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Several getting-started&amp;nbsp;walkthroughs&lt;/li&gt;
&lt;li&gt;Possibly hundreds of&amp;nbsp;examples&lt;/li&gt;
&lt;li&gt;A news feed that regularly links to third-party&amp;nbsp;tutorials&lt;/li&gt;
&lt;li&gt;Thorough &lt;span class="caps"&gt;API&lt;/span&gt;&amp;nbsp;docs&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Perfect.  Beautiful.  Surely, a&amp;nbsp;dream.&lt;/p&gt;
&lt;p&gt;Well,&amp;nbsp;almost.&lt;/p&gt;
&lt;p&gt;The examples are all microscopic, usually focused around a single tiny feature — many of them could be explained just as well with one line of code.  There are a few example games, but they&amp;#8217;re short aimless demos.  None of them are complete &lt;em&gt;games&lt;/em&gt;, and there&amp;#8217;s no showcase either.  Games sometimes pop up in the news feed, but most of them don&amp;#8217;t include source code, so they&amp;#8217;re not useful for learning&amp;nbsp;from.&lt;/p&gt;
&lt;p&gt;Likewise, the &lt;span class="caps"&gt;API&lt;/span&gt; docs are &lt;em&gt;just&lt;/em&gt; &lt;span class="caps"&gt;API&lt;/span&gt; docs, leading to the sorts of problems you might imagine.  For example, in a few places there&amp;#8217;s a mention of&amp;nbsp;a &lt;code&gt;preUpdate&lt;/code&gt; stage that (naturally) happens&amp;nbsp;before &lt;code&gt;update&lt;/code&gt;.  You might rightfully wonder what kinds of things happen&amp;nbsp;in &lt;code&gt;preUpdate&lt;/code&gt; — and more importantly, what should &lt;em&gt;you&lt;/em&gt; put there, and&amp;nbsp;why?&lt;/p&gt;
&lt;p&gt;Let&amp;#8217;s check the &lt;span class="caps"&gt;API&lt;/span&gt; docs for &lt;a href="https://photonstorm.github.io/phaser-ce/Phaser.Group.html#preUpdate"&gt;&lt;code&gt;Phaser.Group.preUpdate&lt;/code&gt;&lt;/a&gt;:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;The core preUpdate - as called by&amp;nbsp;World.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Okay, that didn&amp;#8217;t help too much, but let&amp;#8217;s check what &lt;a href="https://photonstorm.github.io/phaser-ce/Phaser.World.html#preUpdate"&gt;&lt;code&gt;Phaser.World&lt;/code&gt;&lt;/a&gt; has to&amp;nbsp;say:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;The core preUpdate - as called by&amp;nbsp;World.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Ah.  Hm.  It turns&amp;nbsp;out &lt;code&gt;World&lt;/code&gt; is a subclass&amp;nbsp;of &lt;code&gt;Group&lt;/code&gt; and inherits this method — and thus its unaltered docstring —&amp;nbsp;from &lt;code&gt;Group&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;I did eventually find some brief docs attached to &lt;a href="https://photonstorm.github.io/phaser-ce/Phaser.Stage.html#preUpdate"&gt;&lt;code&gt;Phaser.Stage&lt;/code&gt;&lt;/a&gt; (but only by grepping the source code).  It mentions what the framework&amp;nbsp;uses &lt;code&gt;preUpdate&lt;/code&gt; for, but not &lt;em&gt;why&lt;/em&gt;, and not when I might want to use it&amp;nbsp;too.&lt;/p&gt;
&lt;hr /&gt;
&lt;p&gt;The trouble here is that there&amp;#8217;s no narrative documentation — nothing explaining how the library is put together and how I&amp;#8217;m supposed to use it.  I get handed some brief primers and a massive reference, but nothing in between.  It&amp;#8217;s like buying an O&amp;#8217;Reilly book and finding out it only has one chapter followed by a 500-page&amp;nbsp;glossary.&lt;/p&gt;
&lt;p&gt;&lt;span class="caps"&gt;API&lt;/span&gt; docs are great &lt;em&gt;if you know specifically what you&amp;#8217;re looking for&lt;/em&gt;, but they don&amp;#8217;t explain the best way to approach higher-level problems, and they don&amp;#8217;t offer much guidance on how to mesh nicely with the design of a framework or big library.  Phaser does a decent chunk of stuff for you, off in the background somewhere, so it gives the strong impression that it expects you to build around it in a particular way&amp;#8230;  but it never tells you what that way&amp;nbsp;is.&lt;/p&gt;
&lt;h2 id="tutorials"&gt;&lt;a class="toclink" href="#tutorials"&gt;Tutorials&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Ah, but this is what tutorials are for,&amp;nbsp;right?&lt;/p&gt;
&lt;p&gt;I confess I recoil whenever I hear the word &amp;#8220;tutorial&amp;#8221;.  It conjures an image of a uniquely useless sort of post, which goes something like&amp;nbsp;this:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;Look at this cool thing I made!  I&amp;#8217;ll teach you how to do it&amp;nbsp;too.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Press all of these buttons in this order.  Here&amp;#8217;s a screenshot, which looks nothing like what you have, because I&amp;#8217;ve customized the hell out of&amp;nbsp;everything.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;You did&amp;nbsp;it!&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;The author is often less than forthcoming about &lt;em&gt;why&lt;/em&gt; they made any of the decisions they did, where you might want to try something else, or what might go wrong (and how to fix&amp;nbsp;it).&lt;/p&gt;
&lt;p&gt;And this is to be expected!  Writing out any of that stuff requires far more extensive knowledge than you need just to do the thing in the first place, &lt;em&gt;and&lt;/em&gt; you need to do a good bit of introspection to sort out something coherent to&amp;nbsp;say.&lt;/p&gt;
&lt;p&gt;In other words, &lt;strong&gt;teaching is hard.&lt;/strong&gt;  It&amp;#8217;s a skill, and it takes practice, and most people blogging are not experts at it.  Including&amp;nbsp;me!&lt;/p&gt;
&lt;hr /&gt;
&lt;p&gt;With Phaser, I noticed that several of the third-party tutorials I tried to look at were 404s — sometimes less than a year after they were linked on the site.  Pretty major downside to relying on the community for teaching&amp;nbsp;resources.&lt;/p&gt;
&lt;p&gt;But I also notice that&amp;#8230;&amp;nbsp;um&amp;#8230;&lt;/p&gt;
&lt;p&gt;Okay, look.  I &lt;strong&gt;really&lt;/strong&gt; am not trying to rag on this author.  I&amp;#8217;m not.  They tried to share their knowledge with the world, and &lt;em&gt;that&amp;#8217;s a good thing&lt;/em&gt;, something worthy of praise.  I&amp;#8217;m glad they did it!  I hope it helps&amp;nbsp;someone.&lt;/p&gt;
&lt;p&gt;But for the sake of example, &lt;a href="https://phaser.io/news/2017/06/mike-dangers-tutorial-part-2"&gt;here is the most recent entry&lt;/a&gt; in Phaser&amp;#8217;s &lt;a href="https://phaser.io/learn/community-tutorials"&gt;list of community tutorials&lt;/a&gt;.  I &lt;em&gt;have&lt;/em&gt; to link it, because it&amp;#8217;s such a perfect example.&amp;nbsp;Consider:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;The post itself is a bulleted list of explanation followed by a single contiguous 250 lines of source code.  (Not that there&amp;#8217;s anything wrong with bulleted lists, mind you.)  That code contains zero comments and zero blank&amp;nbsp;lines.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;This is only part two in what I think is a series aimed at beginners, yet the title and much of the prose focus on &lt;em&gt;object pooling&lt;/em&gt;, a performance hack that&amp;#8217;s easy to add later and that&amp;#8217;s almost certainly unnecessary for a game this simple.  There is no explanation of &lt;em&gt;why&lt;/em&gt; this is done; the prose only says you&amp;#8217;ll understand why it&amp;#8217;s critical once you add a lot more game&amp;nbsp;objects.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;It turns out I only have two things to say here so I don&amp;#8217;t know why I made this a bulleted&amp;nbsp;list.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;In short, it&amp;#8217;s not really a guided explanation; it&amp;#8217;s &amp;#8220;look what I&amp;nbsp;did&amp;#8221;.&lt;/p&gt;
&lt;p&gt;And that&amp;#8217;s fine, and it can still be interesting.  I&amp;#8217;m not sure English is even this person&amp;#8217;s first language, so I&amp;#8217;m hardly going to criticize them for not writing a novel about&amp;nbsp;platforming.&lt;/p&gt;
&lt;p&gt;The trouble is that I doubt a beginner would walk away from this feeling very enlightened.  They &lt;em&gt;might&lt;/em&gt; be closer to having the game they wanted, so there&amp;#8217;s still value in it, but it feels closer to having someone else do it for them.  And an awful lot of tutorials I&amp;#8217;ve seen — particularly of the &amp;#8220;post on some blog&amp;#8221; form (which I&amp;#8217;m aware is the genre of thing I&amp;#8217;m writing right now) — look&amp;nbsp;similar.&lt;/p&gt;
&lt;p&gt;This isn&amp;#8217;t some huge social problem; it&amp;#8217;s just people writing on their blog and contributing to the corpus of written knowledge.  It &lt;em&gt;does&lt;/em&gt; become a bit stickier when a large project relies on these community tutorials as its main set of teaching&amp;nbsp;aids.&lt;/p&gt;
&lt;hr /&gt;
&lt;p&gt;Again, I&amp;#8217;m not ragging on Phaser here.  I had a slightly frustrating experience with it, coming in knowing what I wanted but unable to find a description of the semantics anywhere, but I do sympathize.  Teaching is hard, writing documentation is hard, and programmers would usually rather &lt;em&gt;program&lt;/em&gt; than do either of those things.  For free projects that run on volunteer work, and in an industry where anything other than programming is a little undervalued, getting good docs written can be&amp;nbsp;tricky.&lt;/p&gt;
&lt;p&gt;(Then again, Phaser sells books and plugins, so maybe they could hire a documentation writer.  Or maybe the whole point is for you to buy the&amp;nbsp;books?)&lt;/p&gt;
&lt;h2 id="some-pretty-good-docs"&gt;&lt;a class="toclink" href="#some-pretty-good-docs"&gt;Some pretty good docs&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Python has pretty good &lt;a href="https://docs.python.org/3/"&gt;documentation&lt;/a&gt;.  It introduces the language with a &lt;a href="https://docs.python.org/3/tutorial/index.html"&gt;tutorial&lt;/a&gt;, then documents everything else in both a library and language&amp;nbsp;reference.&lt;/p&gt;
&lt;p&gt;This sounds an awful lot like Phaser&amp;#8217;s setup, but there&amp;#8217;s some considerable depth in the Python docs.  The tutorial is highly narrative and walks through quite a few corners of the language, stopping to mention common pitfalls and possible use cases.  I clicked &lt;a href="https://docs.python.org/3/tutorial/controlflow.html#more-on-defining-functions"&gt;an arbitrary heading&lt;/a&gt; and found a pleasant, informative read that somehow avoids being bewilderingly&amp;nbsp;dense.&lt;/p&gt;
&lt;p&gt;The &lt;span class="caps"&gt;API&lt;/span&gt; docs also take on a narrative tone — even something as humble as the &lt;a href="https://docs.python.org/3/library/collections.html"&gt;&lt;code&gt;collections&lt;/code&gt; module&lt;/a&gt; offers numerous examples, use cases, patterns, recipes, and hints of interesting ways you might extend the existing&amp;nbsp;types.&lt;/p&gt;
&lt;p&gt;I&amp;#8217;m being a little vague and hand-wavey here, but it&amp;#8217;s hard to give specific examples without just quoting two pages of Python documentation.  Hopefully you can see right away what I mean if you just take a look at them.  They&amp;#8217;re good docs,&amp;nbsp;Bront.&lt;/p&gt;
&lt;p&gt;I&amp;#8217;ve likewise always enjoyed the &lt;a href="http://docs.sqlalchemy.org/en/latest/"&gt;SQLAlchemy documentation&lt;/a&gt;, which follows much the same structure as the main Python documentation.  SQLAlchemy is a database abstraction layer plus &lt;span class="caps"&gt;ORM&lt;/span&gt;, so it can do a &lt;em&gt;lot&lt;/em&gt; of subtly intertwined stuff, and the complexity of the docs reflects this.  Figuring out how to do very advanced things correctly, in particular, can be challenging.  But for the most part it does a very thorough job of introducing you to a large library with a particular philosophy and how to best work alongside&amp;nbsp;it.&lt;/p&gt;
&lt;p&gt;I softly contrast this with, say, the Perl&amp;nbsp;documentation.&lt;/p&gt;
&lt;p&gt;It&amp;#8217;s gotten better since I first learned Perl, but Perl&amp;#8217;s docs are still a bit of a strange beast.  They exist as a flat collection of manpage-like documents with terse names like &lt;a href="http://perldoc.perl.org/perlootut.html"&gt;perlootut&lt;/a&gt;.  The documentation is certainly thorough, but much of it has a strange&amp;#8230;  allocation of&amp;nbsp;detail.&lt;/p&gt;
&lt;p&gt;For example, &lt;a href="http://perldoc.perl.org/perllol.html#Growing-Your-Own"&gt;perllol&lt;/a&gt; — the explanation of how to make a list of lists, which somehow merits its own separate documentation — offers no fewer than &lt;em&gt;nine&lt;/em&gt; similar variations of the same code for reading a file into a nested lists of words on each line.  Where Python offers examples for a variety of different problems, Perl shows you a lot of subtly different ways to do the same basic&amp;nbsp;thing.&lt;/p&gt;
&lt;p&gt;A similar problem is that Perl&amp;#8217;s docs sometimes offer far too much context; consider the &lt;a href="http://perldoc.perl.org/perlreftut.html"&gt;references tutorial&lt;/a&gt;, which starts by explaining that references are a powerful &amp;#8220;new&amp;#8221; feature in Perl 5 (first released in 1994).  It then explains why you might want to nest data structures&amp;#8230;  from a Perl 4 perspective, thus explaining why Perl 5 is so much&amp;nbsp;better.&lt;/p&gt;
&lt;h2 id="some-stuff-ive-tried"&gt;&lt;a class="toclink" href="#some-stuff-ive-tried"&gt;Some stuff I've tried&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;I don&amp;#8217;t claim to be a great teacher.  I like to talk about stuff I find interesting, and I try to do it in ways that are accessible to people who aren&amp;#8217;t lugging around the mountain of context I already have.  This being just some blog, it&amp;#8217;s hard to tell how well that works, but I do my&amp;nbsp;best.&lt;/p&gt;
&lt;p&gt;I also know that I learn best when I can &lt;em&gt;understand&lt;/em&gt; what&amp;#8217;s going on, rather than just seeing surface-level cause and effect.  Of course, with complex subjects, it&amp;#8217;s hard to develop an understanding before you&amp;#8217;ve seen the cause and effect a few times, so there&amp;#8217;s a balancing act between showing examples and trying to provide an explanation.  Too many concrete examples feel like rote memorization; too much abstract theory feels disconnected from anything&amp;nbsp;tangible.&lt;/p&gt;
&lt;p&gt;The attempt I&amp;#8217;m most pleased with is probably my &lt;a href="https://eev.ee/blog/2016/05/29/perlin-noise/"&gt;post on Perlin noise&lt;/a&gt;.  It covers a fairly specific subject, which made it much easier.  It builds up one step at a time from scratch, with visualizations at every point.  It offers some interpretations of what&amp;#8217;s going on.  It clearly explains some possible extensions to the idea, but distinguishes those from the core&amp;nbsp;concept.&lt;/p&gt;
&lt;p&gt;It &lt;em&gt;is&lt;/em&gt; a little math-heavy, I grant you, but that was hard to avoid with a fundamentally mathematical topic.  I had to be economical with the background information, so I let the math be a little dense in&amp;nbsp;places.&lt;/p&gt;
&lt;p&gt;But the best part about it by far is that &lt;em&gt;I&lt;/em&gt; learned a lot about Perlin noise in the process of writing it.  In several places I realized I couldn&amp;#8217;t explain what was going on in a satisfying way, so I had to dig deeper into it before I could write about it.  Perhaps there&amp;#8217;s a good guideline hidden in there: don&amp;#8217;t try to teach as much as you&amp;nbsp;know?&lt;/p&gt;
&lt;p&gt;I&amp;#8217;m also fairly happy with my series on &lt;a href="https://eev.ee/blog/2015/12/19/you-should-make-a-doom-level-part-1/"&gt;making Doom maps&lt;/a&gt;, though they meander into tangents a little more often.  It&amp;#8217;s hard to talk about something like Doom &lt;em&gt;without&lt;/em&gt; meandering, since it&amp;#8217;s a convoluted ecosystem that&amp;#8217;s grown organically over the course of 24 years and has at least three ways of doing&amp;nbsp;anything.&lt;/p&gt;
&lt;hr /&gt;
&lt;p&gt;And finally there&amp;#8217;s the book I&amp;#8217;m trying to write, which is sort of about game&amp;nbsp;development.&lt;/p&gt;
&lt;p&gt;One of my biggest grievances with game development teaching in particular is how often it leaves out important touches.  Very few guides will tell you how to make a title screen or menu, how to handle death, how to get a Mario-style variable jump height.  They&amp;#8217;ll show you how to build a clearly unfinished demo game, then leave you to your own&amp;nbsp;devices.&lt;/p&gt;
&lt;p&gt;I realized that the only reliable way to show how to build a game is to &lt;em&gt;build a real game&lt;/em&gt;, then write about it.  So the book is laid out as a narrative of how I wrote my first few games, complete with stumbling blocks and dead ends and tiny bits of&amp;nbsp;polish.&lt;/p&gt;
&lt;p&gt;I have no idea how well this will work, or whether recapping my own mistakes will be interesting or distracting for a beginner, but it ought to be an interesting&amp;nbsp;experiment.&lt;/p&gt;</content><category term="articles"></category><category term="tech"></category><category term="patreon"></category></entry></feed>