<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0"><channel><title><![CDATA[Mark Vasilkov]]></title><description><![CDATA[Mark Vasilkov]]></description><link>https://mvasilkov.animuchan.net</link><generator>RSS for Node</generator><lastBuildDate>Fri, 23 Feb 2024 07:55:10 GMT</lastBuildDate><atom:link href="https://mvasilkov.animuchan.net/rss.xml" rel="self" type="application/rss+xml"/><language><![CDATA[en]]></language><ttl>60</ttl><atom:link rel="next" href="https://mvasilkov.animuchan.net/rss.xml?page=1"/><item><title><![CDATA[Super Castle Game (js13kGames–2023)]]></title><description><![CDATA[<blockquote><p>An open mind is like a fortress with its gates unbarred and unguarded.</p></blockquote><p>This is the story of the <a target="_blank" href="https://dev.js13kgames.com/2023/games/super-castle-game">Super Castle Game</a>, a <a target="_blank" href="https://js13kgames.com/">js13k game jam</a> entry.</p><p>Naturally, it begins with the theme of the 2023 edition of the compo: <em>13th Century.</em> When it <a target="_blank" href="https://medium.com/js13kgames/js13kgames-2023-has-started-b4a25886d082">dropped</a>, I was completely lost. Medieval history is like Warhammer 40,000 to me, where in the grim darkness of ages past, there is mostly war. Doesn't give off warm fuzzies.</p><p>So I began coding a simple board game thing: a square grid with pieces on it. Such an enjoyable experience, that. Writing clean code for small, easy to understand systems.</p><div class="embed-wrapper"><div class="embed-loading"><div class="loadingRow"></div><div class="loadingRow"></div></div><a class="embed-card" href="https://twitter.com/mvasilkov/status/1691495601437913104">https://twitter.com/mvasilkov/status/1691495601437913104</a></div><p> </p><h2 id="heading-smooth-brain-energy">Smooth brain energy</h2><p>Herein lies my first mistake, thinking that a square-grid-based game will be easy to make. Full of unwarranted enthusiasm, I added clusters of pieces that should move together as a single large object, and whipped up some sort of a collision solver: a piece pushes another piece, so we recursively solve for that piece moving. And its entire cluster moving.</p><p>So when there are two interlocked clusters, they push one another into a stack overflow. This terrible algorithm haunted me for the entirety of the compo, and I'm still unsure how to implement it properly, edge cases and all. In a feat of peak gamedev, I settled on making levels that don't break the solver, and that was it.</p><h2 id="heading-of-ducks-and-men">Of ducks and men</h2><p>At this stage the game spontaneously ended up being about a duck going on a crusade, under the working title of <em>Super Holy Chalice</em> and bearing the following description:</p><ul><li><p>Embark on a crusade</p></li><li><p>Solve challenges</p></li><li><p>Reclaim the Holy Chalice</p></li><li><p>In the 13th century</p></li></ul><p>To be honest I wasn't feeling it, so at work I discussed the matter with a brilliant engineer on my team, who suggested a different take: the siege of Baghdad. We're a scholar at the House of Wisdom, about to be sacked by the culturally diverse Mongol migrants. We need to prevent this from happening using magic. Puzzle solving is the compulsory magic ritual.</p><p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1694856154049/0eb1a927-e43c-4a9c-bef4-5ef51e8af01a.png" alt="Super Siege of Baghdad title screen" /></p><h3 id="heading-super-siege-of-baghdad">Super Siege of Baghdad</h3><blockquote><p>In the heart of ancient Baghdad, a shining beacon of knowledge, the House of Wisdom, stands as a testament to the golden age of enlightenment. As the Mongol forces close in on the city in 1258, threatening to obliterate centuries of accumulated wisdom, one scholar embarks on a mission  wielding not a sword, but the power of sacred geometry.</p><p>Set in the 13th century, <em>Super Siege of Baghdad</em> immerses you in a world where geometry is more than an empirical science; it's a mystical force with the potential to change the course of history. Can you harness this power and save the House of Wisdom from its impending doom?</p></blockquote><p>I don't hate this fluff piece, but to me it feels way forced. This is not something I would've considered if not for the compo theme.</p><p>When talking about this issue with a friend, the third and final take on the <em>13th Century</em> theme was born: a game about building a castle. Castles are vaguely medieval. The level is a quarry, where you arrange the stone blocks. Suddenly it all made sense.</p><h2 id="heading-super-castle-game">Super Castle Game</h2><p><em>The fortress of the mind will rise</em></p><p>Now I needed to render a castle. Isometric projection in hand, I made this model by starting with a cube and poking holes in it for the gate, windows and battlements on top. This is only done once while the game is loading, then the model is stored as three one-dimensional arrays for x, y and z coordinates of each block.</p><p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1694856285171/5f372113-b599-4dfc-bd59-e277f196d547.png" alt="Super Castle Game title screen" /></p><p>Optimizing the rendering of this thing was a fun little challenge. I'm working with the regular 2D canvas, where each separate draw call is expensive, and acceptable performance is achieved by making a single large path and then filling it all at once. But at the same time I rely on rendering the model back to front, <em>one block at a time,</em> else it looks all messed up.</p><p>To stress test the rendering (so that the effect of any optimizations is easier to see), I ran a solid cube 121212, or about 1700 blocks. Three sides per block are visible, amounting to roughly 20,000 calls to <code>.lineTo()</code> and 5000 calls to <code>.fill()</code>.</p><p>You can imagine it was REALLY slow in this setting. We're talking barely 30 FPS on a half-decent Intel MacBook, and budget smartphones would be seeing fractional frame rates. So I tried two things to make it faster:</p><ol><li><p>Group the <code>.fill()</code> calls so that a run of blocks can be filled with just three calls <em>per run,</em> down from three calls <em>per individual block.</em> This only works if there's no depth order fighting, so I made it render vertical columns of blocks together: first the top side of each block (this is important!), and then right and left sides. And it's super effective! Like I anticipated, calls that add a subpath are cheap, it's <code>.fill()</code> and <code>.stroke()</code> that are costly.</p></li><li><p>Then, in the mood for some more performance optimization, I made a hash function for blocks to check if they're occupying the same exact spot on screen. The function is rather straightforward: <code>(x - y) * 999 + (x + y - z - z)</code>. With this, culling the blocks that are fully covered by others closer to the camera became very easy. I also decided to bake the culling into the model itself, since there's no camera rotation anyway. This reduced the number of blocks by about 20% when applied to my actual castle model, so it's a small win.</p></li></ol><p>After these changes, the test bench ran smoothly and capped at the screen refresh rate on any computer I tried.</p><h2 id="heading-level-design">Level design</h2><p>This entire performance story is, of course, about procrastination: instead of doing the hard thing, such as making levels that are fun to play, I worked on parts that are fun to fiddle with. So now I had a fast isometric renderer for the title screen, but no level design for the bulk of the game.</p><p>Here I made a decision which, for a change, I didn't regret immediately: to use Aseprite for a level editor. If you're not familiar, <a target="_blank" href="https://aseprite.org/">Aseprite</a> is an open source pixel art editor, and it's as gorgeous as it is convenient to work in.</p><p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1694856753622/b990b5ed-d90f-46ed-96d1-610fa1269207.png" alt="Using Aseprite as a level editor" class="image--center mx-auto" /></p><p>Having made a Python script to convert PNG files to a <a target="_blank" href="https://mvasilkov.animuchan.net/bigint-embedded-bitmap-encoding">BigInt EEBE</a> format, I now had a toolchain to iterate on the levels very efficiently. And iterate on the levels I did: turns out it's so hard to come up with a clever puzzle! Who would've thunk.</p><p>Some of my earlier designs were laughably broken. One of the stages I thought was impressively challenging could be solved by pressing, quite literally, right, up, right. That's it, that's the entire solution. I have failed to see this (elegant, but very much unintended) answer for the longest time, and only discovered it thanks to a teammate, whom I coerced into early playtesting.</p><h2 id="heading-decentralized">Decentralized</h2><p>This time I wanted to make a real appearance in the <a target="_blank" href="https://js13kgames.com/decentralized">Decentralized category</a> of the compo, so I decided I'll make a <a target="_blank" href="https://reirei.neocities.org/editor">level editor</a>. Levels are relatively compact, so I put them in the URL of the game. This way anyone can make a level and share it via any means: on a social network, git repository, by email, or a graffiti of a QR code on a neighbor's house. (Please send me a picture of that!)</p><p>There's a <a target="_blank" href="https://github.com/mvasilkov/super2023/tree/master/levels#super-castle-game-community-levels">Community Levels page</a> with a collection of stages made by you. Send pull requests, or just drop me a link to the stage you've made, and I'll add it.</p><h3 id="heading-interplanetary">InterPlanetary</h3><p>The game is also available on IPFS: <strong>ipfs://bafybeie5x57m5gdh6r3bz7wnfeifo22wvcb2efas2l5hplvnwl5vz6myua</strong></p><p>There's <a target="_blank" href="https://bafybeie5x57m5gdh6r3bz7wnfeifo22wvcb2efas2l5hplvnwl5vz6myua.ipfs.w3s.link/">a</a> <a target="_blank" href="https://cloudflare-ipfs.com/ipfs/bafybeie5x57m5gdh6r3bz7wnfeifo22wvcb2efas2l5hplvnwl5vz6myua/">number</a> <a target="_blank" href="https://bafybeie5x57m5gdh6r3bz7wnfeifo22wvcb2efas2l5hplvnwl5vz6myua.ipfs.cf-ipfs.com/">of</a> <a target="_blank" href="https://gateway.ipfs.io/ipfs/bafybeie5x57m5gdh6r3bz7wnfeifo22wvcb2efas2l5hplvnwl5vz6myua/">HTTP</a> <a target="_blank" href="https://bafybeie5x57m5gdh6r3bz7wnfeifo22wvcb2efas2l5hplvnwl5vz6myua.ipfs.dweb.link/">gateways</a> to choose from, in case your browser doesn't support IPFS. (Most of them don't.)</p><h2 id="heading-coding-highlights">Coding highlights</h2><p>In case you're interested in low-level stuff, in no particular order:</p><p><em>Super Castle Game</em> uses <a target="_blank" href="https://github.com/mvasilkov/natlib">natlib</a>, a highly composable library for small games. I wrote both, so it's not a sound endorsement.</p><p>I also run <a target="_blank" href="https://github.com/mvasilkov/michikoid">Michikoid</a>, a JavaScript macro processor that allows me to manually control how things are inlined in the resulting JS bundle. (I made this one too.)</p><p>Speaking of build, this year I wrote my build scripts in both bash and PowerShell. The PowerShell one is very slow for no apparent reason, but it made switching between a MacBook and a Windows PC so easy that I'm not even mad. Compared to Windows Subsystem for Linux, which is low key abominable, PowerShell hits different.</p><p>Having no artistic ability whatsoever, I chose the Sweetie 16 palette by <a target="_blank" href="https://grafxkid.itch.io/">GrafxKid</a> as my main driver. Best choice ever made in the history of choices.</p><div class="embed-wrapper"><div class="embed-loading"><div class="loadingRow"></div><div class="loadingRow"></div></div><a class="embed-card" href="https://twitter.com/mvasilkov/status/1696571651125620993">https://twitter.com/mvasilkov/status/1696571651125620993</a></div><p> </p><p>The pixel font is from the <a target="_blank" href="https://tic80.com/">TIC-80 fantasy computer</a>.</p><p>There's also gamepad support, and turns out it's <a target="_blank" href="https://github.com/mvasilkov/super2023/blob/ebd3feaf4dd8109697a574a63b1953fed1c19b2e/super2023/typescript/gamepad.ts">really easy</a> to implement! Of course I only read the left analog stick and the B button, but it's still impressive how straightforward the Gamepad API is, especially compared to others. (I am truly convinced that the Web Audio API was designed by a committee of sentient guacamoles from hell.)</p><p>Anyway, have fun with the <a target="_blank" href="https://github.com/mvasilkov/super2023/tree/master/super2023/typescript">source code</a>, it's a tangled mess! I'll try to clean up some of the more egregious parts. It's under the GPLv3 license, so feel free (as in freedom) to do whatever.</p><h2 id="heading-fin">Fin</h2><p>Thank you for reading! Or scrolling to the end! If the js13k voting is still happening, please <a target="_blank" href="https://dev.js13kgames.com/2023/games/super-castle-game">play <em>Super Castle Game</em></a> and vote. Then play other games, too, there are some truly phenomenal ones in there.</p><p>Follow me on <a target="_blank" href="https://github.com/mvasilkov">GitHub</a> for more spaghetti code.</p>]]></description><link>https://mvasilkov.animuchan.net/super-castle-game-js13kgames2023</link><guid isPermaLink="true">https://mvasilkov.animuchan.net/super-castle-game-js13kgames2023</guid><dc:creator><![CDATA[Mark Vasilkov]]></dc:creator><pubDate>Sat, 16 Sep 2023 09:55:55 GMT</pubDate><cover_image>https://cdn.hashnode.com/res/hashnode/image/upload/v1694855734008/20e4267f-b5c4-4d61-ba27-9ae242084b5c.png</cover_image></item><item><title><![CDATA[Language Model API Parameters]]></title><description><![CDATA[<p>An application talking to a language model API has control over the following parameters. Only the Vertex AI PaLM API allows setting Top-K and Top-P at the time of writing.</p><h2 id="heading-temperature">Temperature</h2><p>The temperature (a floating-point number in the range <code>0.0</code><code>1.0</code>) is used for sampling during response generation, which occurs when <code>topP</code> and <code>topK</code> are applied. Temperature controls the degree of randomness in token selection. Lower temperatures are good for prompts that require a more deterministic and less open-ended or creative response, while higher temperatures can lead to more diverse or creative results. A temperature of <code>0</code> is deterministic, meaning that the highest probability response is always selected.</p><h2 id="heading-maximum-output-tokens">Maximum Output Tokens</h2><p>Maximum number of tokens that can be generated in the response. A token is approximately four characters. 100 tokens correspond to roughly 6080 words.</p><ul><li><p>For OpenAI, this parameter should be in the range <code>1</code><code>2048</code></p></li><li><p>For PaLM, this parameter should be in the range <code>1</code><code>1024</code></p></li></ul><h2 id="heading-top-k">Top-K</h2><p>Top-K (an integer in the range <code>1</code><code>40</code>) changes how the model selects tokens for output. A top-K of <code>1</code> means the next selected token is the most probable among all tokens in the model's vocabulary (also called greedy decoding), while a top-K of <code>3</code> means that the next token is selected from among the three most probable tokens by using temperature.</p><p>For each token selection step, the top-K tokens with the highest probabilities are sampled. Then tokens are further filtered based on top-P with the final token selected using temperature sampling.</p><p>Specify a lower value for more deterministic responses and a higher value for more diverse responses. The default top-K is <code>40</code>.</p><h2 id="heading-top-p">Top-P</h2><p>Top-P (a floating-point number in the range <code>0.0</code><code>1.0</code>) changes how the model selects tokens for output. Tokens are selected from the most (see top-K) to least probable until the sum of their probabilities equals the top-P value. For example, if tokens A, B, and C have a probability of 0.3, 0.2, and 0.1 and the top-P value is <code>0.5</code>, then the model will select either A or B as the next token by using temperature and excludes C as a candidate.</p><p>Specify a lower value for more deterministic responses and a higher value for more diverse responses. The default top-P is <code>0.95</code>.</p>]]></description><link>https://mvasilkov.animuchan.net/language-model-api-parameters</link><guid isPermaLink="true">https://mvasilkov.animuchan.net/language-model-api-parameters</guid><dc:creator><![CDATA[Mark Vasilkov]]></dc:creator><pubDate>Wed, 31 May 2023 13:03:11 GMT</pubDate><cover_image>https://cdn.hashnode.com/res/hashnode/image/stock/unsplash/oEN_iY1Fy4I/upload/4c741608ebc71628f5104482b40e6b51.jpeg</cover_image></item><item><title><![CDATA[CSV field larger than field limit in Python]]></title><description><![CDATA[<p>So I was working on something very simple involving a CSV file:</p><pre><code class="lang-python"><span class="hljs-keyword">import</span> csv<span class="hljs-keyword">with</span> open(<span class="hljs-string">'file.csv'</span>, <span class="hljs-string">'r'</span>) <span class="hljs-keyword">as</span> f:    reader = csv.reader(f)    lines = [ln <span class="hljs-keyword">for</span> ln <span class="hljs-keyword">in</span> reader]</code></pre><p>And as one would expect, the result of running this code was <code>Error: field larger than field limit (131072)</code></p><p>The 128 KiB field size boundary is so beautifully arbitrary. Use the following workaround to fix that:</p><pre><code class="lang-python"><span class="hljs-keyword">import</span> syscsv.field_size_limit(sys.maxsize)</code></pre><p>Doesn't have to be specifically <code>sys.maxsize</code>, any sufficiently large number would do.</p><p>There's no moral to this story, just mild suffering.</p>]]></description><link>https://mvasilkov.animuchan.net/python-csv-field-larger-than-field-limit</link><guid isPermaLink="true">https://mvasilkov.animuchan.net/python-csv-field-larger-than-field-limit</guid><dc:creator><![CDATA[Mark Vasilkov]]></dc:creator><pubDate>Mon, 24 Apr 2023 13:09:27 GMT</pubDate><cover_image>https://cdn.hashnode.com/res/hashnode/image/stock/unsplash/jiqbWnkkgzI/upload/0fe268d4aafaefba6b30ae18293b0eee.jpeg</cover_image></item><item><title><![CDATA[ZX Spectrum system font]]></title><description><![CDATA[<p>Old computer fonts are fascinating. Making a consistent-looking font is a remarkable achievement in its own right, but designing a monospaced font that is readable on a very low-resolution screen, is aesthetically pleasing, and has a distinct personality  that's a whole other level of difficulty.</p><p>The ZX Spectrum system font is a nice example of this. It's an 8x8 pixel bitmap font used on the ZX Spectrum, a home computer released in the United Kingdom in 1982, and it's still recognized today (more than 40 years later!) as part of the machine's unique visual identity.</p><p>I mean, just look at it:</p><p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1676281955861/8326886f-c5b8-4623-b0f8-c1c989da6fd0.png" alt class="image--center mx-auto" /></p><p>Sure, the font is a bit of a mess, but it's a charming mess.</p><p>By the way, the screenshots in this post were made using <a target="_blank" href="https://fuse-emulator.sourceforge.net/">Fuse</a>, the Free Unix Spectrum Emulator. It's a delightful application, and I had a blast playing around with it.</p><p>The Sinclair BASIC program producing the output above is as follows:</p><p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1676281982838/0687580a-51bb-4f87-b40c-8c8d6297dc47.png" alt class="image--center mx-auto" /></p><p>Typing this in on a regular keyboard was interesting: the BASIC keywords are their own code points, so pressing P while in keyword mode produces <code>PRINT</code> as a single character, R is <code>RUN</code>, and so on. Had to look some of them up, since I never remembered the ZX Spectrum's keyboard layout in the first place.</p><hr /><p>To better appreciate the original font design, compare it to a modern ZX Spectrum font, <a target="_blank" href="http://www.type-invaders.com/sinclair/clairsys/">Clairsys</a>, designed by Paul van der Laan in 2002:</p><p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1676282045135/8112a049-49ee-46fd-a18d-c175ecb09dc5.png" alt class="image--center mx-auto" /></p><p>Clairsys features less blocky letters, generally narrower than in the original font, and with larger counters. The letter spacing, as a result, is super wide.</p><p>And here's the same one-liner from before, typeset in Clairsys:</p><p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1676282069696/0c91eec1-5192-459a-bf18-879cefcd1411.png" alt class="image--center mx-auto" /></p><p>Dotted zero is a retribution for our collective sins. Other than that, I don't have any overarching conclusions for this post.</p><p>I'll try to make this into a series on pixel fonts. Let's see how that goes.</p>]]></description><link>https://mvasilkov.animuchan.net/zx-spectrum-system-font</link><guid isPermaLink="true">https://mvasilkov.animuchan.net/zx-spectrum-system-font</guid><dc:creator><![CDATA[Mark Vasilkov]]></dc:creator><pubDate>Mon, 13 Feb 2023 10:04:35 GMT</pubDate><cover_image>https://cdn.hashnode.com/res/hashnode/image/upload/v1676282195495/d5767068-3c19-435e-8b1c-a9e28541f657.png</cover_image></item><item><title><![CDATA[BigInt Embedded Bitmap Encoding]]></title><description><![CDATA[<p>This is the second post in the series. See the previous post, <a target="_blank" href="https://mvasilkov.animuchan.net/ecmascript-embedded-bitmap-encoding">ECMAScript Embedded Bitmap Encoding</a> for context.</p><hr /><p>My friend <a target="_blank" href="https://github.com/romankoblov">Roman</a> pointed out that I was, in fact, doing things suboptimally in the EEBE post:</p><ul><li>The array of scanlines can be replaced with a single BigInt literal, bringing substantially less syntactic overhead.</li><li>The use of integer bpp for color depth can be wasteful if the number of colors isn't a power of two, and we're not seeing any performance gains from it either. More compact results can be achieved by using the number of distinct colors directly.</li></ul><p>The updated example is as follows:</p><pre><code class="lang-javascript"><span class="hljs-comment">// ECMAScript Embedded Bitmap Encoding (EEBE)</span><span class="hljs-comment">// Required fields:</span><span class="hljs-keyword">export</span> <span class="hljs-keyword">const</span> value = <span class="hljs-number">0x61e3c70891a1c08n</span><span class="hljs-keyword">export</span> <span class="hljs-keyword">const</span> width = <span class="hljs-number">7</span><span class="hljs-keyword">export</span> <span class="hljs-keyword">const</span> height = <span class="hljs-number">9</span><span class="hljs-keyword">export</span> <span class="hljs-keyword">const</span> cardinality = <span class="hljs-number">2</span><span class="hljs-comment">// Optional fields:</span><span class="hljs-keyword">export</span> <span class="hljs-keyword">const</span> palette = [  <span class="hljs-number">0x000000</span>,  <span class="hljs-number">0xffffff</span>,]</code></pre><p>The following fields are exported:</p><ul><li><code>value</code> is a BigInt literal containing the bitmap contents.</li><li><code>width</code> and <code>height</code> are self-explanatory.</li><li><code>cardinality</code> is the number of distinct colors in the bitmap.</li><li><code>palette</code> is an optional array of integers, each representing a color.</li></ul><p><a target="_blank" href="https://codepen.io/mvasilkov/full/xxJvEWN">The updated playground for this version is here.</a></p>]]></description><link>https://mvasilkov.animuchan.net/bigint-embedded-bitmap-encoding</link><guid isPermaLink="true">https://mvasilkov.animuchan.net/bigint-embedded-bitmap-encoding</guid><dc:creator><![CDATA[Mark Vasilkov]]></dc:creator><pubDate>Sat, 11 Feb 2023 13:54:04 GMT</pubDate><cover_image>https://cdn.hashnode.com/res/hashnode/image/upload/v1676123597329/6e1a6441-2c9a-4c01-bfd3-0d340c533197.png</cover_image></item><item><title><![CDATA[ECMAScript Embedded Bitmap Encoding]]></title><description><![CDATA[<p>I propose the following bitmap format, suitable for embedding small images in TypeScript or JavaScript source code:</p><pre><code class="lang-javascript"><span class="hljs-comment">// ECMAScript Embedded Bitmap Encoding (EEBE)</span><span class="hljs-comment">// Required fields:</span><span class="hljs-keyword">export</span> <span class="hljs-keyword">const</span> lines = [  <span class="hljs-number">0b0001000</span>,  <span class="hljs-number">0b0111000</span>,  <span class="hljs-number">0b1101000</span>,  <span class="hljs-number">0b1001000</span>,  <span class="hljs-number">0b0001000</span>,  <span class="hljs-number">0b0001110</span>,  <span class="hljs-number">0b0001111</span>,  <span class="hljs-number">0b0001111</span>,  <span class="hljs-number">0b0000110</span>,]<span class="hljs-keyword">export</span> <span class="hljs-keyword">const</span> width = <span class="hljs-number">7</span><span class="hljs-keyword">export</span> <span class="hljs-keyword">const</span> bpp = <span class="hljs-number">1</span><span class="hljs-comment">// Optional fields:</span><span class="hljs-keyword">export</span> <span class="hljs-keyword">const</span> palette = [  <span class="hljs-number">0x000</span>,  <span class="hljs-number">0xfff</span>,]</code></pre><p>Let's call it <em>ECMAScript Embedded Bitmap Encoding</em> (EEBE). EEBE is a format used to store bitmap images within ECMAScript code in an efficient manner. It is designed to be compact and easy to parse, suitable for scenarios with hard size constraints such as <a target="_blank" href="https://js13kgames.com/">js13kGames</a>, and isn't at all intended to be a general purpose image format.</p><p>An EEBE file is an ECMAScript module that exports the following fields:</p><ul><li><p><code>lines</code>: an array of integers, each representing a scanline of the image. Each integer is a bitfield of size <code>width * bpp</code>, with the least significant bit(s) representing the <strong>leftmost</strong> pixel of the scanline.</p></li><li><p><code>width</code>: the width of the image in pixels.</p></li><li><p><code>bpp</code>: the number of bits per pixel.</p></li><li><p><code>palette</code>: an optional array of integers, each representing a color. The number of colors in the palette should equal <code>2 ** bpp</code>. Nullish values are treated as transparent. If this field is omitted, the rendering is implementation-defined.</p></li><li><p>Any other fields, depending on the implementation.</p></li></ul><p>Optimized for size, the above example is 116 bytes long (99 bytes gzipped) and produces the following image:</p><p><a target="_blank" href="https://codepen.io/mvasilkov/full/WNKmPqR"><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1675779459552/5d4f8957-995f-4181-9a85-7dd630b7a9c8.png" alt /></a></p><p><a target="_blank" href="https://codepen.io/mvasilkov/full/WNKmPqR">EEBE Playground</a></p><pre><code class="lang-javascript"><span class="hljs-comment">// This is the same example as above, written in a compact form.</span><span class="hljs-keyword">export</span> <span class="hljs-keyword">const</span> lines=[<span class="hljs-number">8</span>,<span class="hljs-number">56</span>,<span class="hljs-number">104</span>,<span class="hljs-number">72</span>,<span class="hljs-number">8</span>,<span class="hljs-number">14</span>,<span class="hljs-number">15</span>,<span class="hljs-number">15</span>,<span class="hljs-number">6</span>]<span class="hljs-keyword">export</span> <span class="hljs-keyword">const</span> width=<span class="hljs-number">7</span><span class="hljs-keyword">export</span> <span class="hljs-keyword">const</span> bpp=<span class="hljs-number">1</span><span class="hljs-keyword">export</span> <span class="hljs-keyword">const</span> palette=[<span class="hljs-number">0</span>,<span class="hljs-number">4095</span>]</code></pre><p>The footprint can be further reduced during build:</p><ul><li>Using a module bundler such as <a target="_blank" href="https://rollupjs.org/">Rollup</a> will eliminate the <code>export</code> statements.</li><li>Running a minifier (e.g. <a target="_blank" href="https://terser.org/">Terser</a>) will shorten the field names and inline some of the constants.</li></ul><p>It's also possible to save bytes by using 12-bit color (<code>#9d5</code> instead of <code>#99dd55</code>) and sharing the palette between multiple images.</p><h2 id="heading-decoding-the-image">Decoding the image</h2><p>An EEBE image can be decoded as follows:</p><pre><code class="lang-javascript"><span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">readBitmap</span>(<span class="hljs-params">lines, width, bpp, readFunction</span>) </span>{  <span class="hljs-keyword">const</span> shift = <span class="hljs-number">1</span> &lt;&lt; bpp  <span class="hljs-keyword">for</span> (<span class="hljs-keyword">let</span> y = <span class="hljs-number">0</span>; y &lt; lines.length; ++y) {    <span class="hljs-keyword">for</span> (<span class="hljs-keyword">let</span> x = <span class="hljs-number">0</span>; x &lt; width; ++x) {      <span class="hljs-keyword">const</span> value = lines[y] / shift ** x &amp; shift - <span class="hljs-number">1</span>      readFunction(x, y, value)    }  }}</code></pre><p>(This function is available in <a target="_blank" href="https://github.com/mvasilkov/natlib/blob/master/typescript/bitmap/bitmap.ts">natlib</a>.)</p><p>For each pixel in the image, the <code>readFunction(x, y, value)</code> is invoked. The pixel's <code>value</code> is an integer in the range <code>[0, 2 ** bpp - 1]</code> that can be used to retrieve the corresponding color from the palette.</p><p>You might be wondering why the <code>readBitmap</code> function uses the division operator instead of a <code>&lt;&lt;</code> bit shift. It's because the bitwise operations in JS truncate their operands to 32 bits, whereas the Number type is a 64-bit floating point, allowing for integer values of up to 53 bits. So the tradeoff here is that we can either</p><ul><li>Enjoy the full 53-bit stride for images, but use the (slower) division operator, or</li><li>Use the fast <code>&lt;&lt;</code> bit shift operator, but only get 32 bits of stride.</li></ul><h2 id="heading-real-world-usage">Real-world usage</h2><p>I've used this format in my js13kGames entries, including <a target="_blank" href="https://js13kgames.com/entries/the-neatness">The Neatness</a>, with good results.</p><p>It's clear that variations of this  storing bitmaps as <code>int[]</code>  have been in use since at least ZX Spectrum days. The motivation for writing this short spec and giving EEBE its name is to promote interoperability, not to claim originality.</p><h2 id="heading-questions">Questions</h2><p>Absolutely none of these questions have been asked.</p><h3 id="heading-what-about-extensibility">What about extensibility?</h3><p>You can include extra fields. For example, in <a target="_blank" href="https://mvasilkov.animuchan.net/the-neatness-js13kgames2022">The Neatness</a> levels contain hotspots (entry and exit points) in addition to the level geometry represented by the bitmap.</p><h3 id="heading-wheres-the-tooling">Where's the tooling?</h3><p>It's in the planning phase.</p><h3 id="heading-how-to-pronounce-eebe">How to pronounce "EEBE"?</h3><p>Try <code>[jebi]</code>.</p><h3 id="heading-why-did-the-chicken-cross-the-road">Why did the chicken cross the road?</h3><p>To gather intelligence. It was a <a target="_blank" href="https://twitter.com/RealSexyCyborg/status/1621323329721487360">Chinese surveillance chicken</a>.</p>]]></description><link>https://mvasilkov.animuchan.net/ecmascript-embedded-bitmap-encoding</link><guid isPermaLink="true">https://mvasilkov.animuchan.net/ecmascript-embedded-bitmap-encoding</guid><dc:creator><![CDATA[Mark Vasilkov]]></dc:creator><pubDate>Tue, 07 Feb 2023 14:53:48 GMT</pubDate><cover_image>https://cdn.hashnode.com/res/hashnode/image/upload/v1676123794031/2657d321-039f-4b36-bad3-6296f1478996.png</cover_image></item><item><title><![CDATA[TypeScript Positive Integer Type]]></title><description><![CDATA[<p>This is how you define the <code>PositiveInteger</code> dependent type in TypeScript without using type predicate functions or branded (tagged) types:</p><pre><code class="lang-typescript"><span class="hljs-keyword">type</span> PositiveInteger&lt;T <span class="hljs-keyword">extends</span> <span class="hljs-built_in">number</span>&gt; =  <span class="hljs-string">`<span class="hljs-subst">${T}</span>`</span> <span class="hljs-keyword">extends</span> <span class="hljs-string">'0'</span> | <span class="hljs-string">`-<span class="hljs-subst">${<span class="hljs-built_in">any</span>}</span>`</span> | <span class="hljs-string">`<span class="hljs-subst">${<span class="hljs-built_in">any</span>}</span>.<span class="hljs-subst">${<span class="hljs-built_in">any</span>}</span>`</span> ? <span class="hljs-built_in">never</span> : T</code></pre><p>It can then be used as follows:</p><pre><code class="lang-typescript"><span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">test</span>&lt;<span class="hljs-title">T</span> <span class="hljs-title">extends</span> <span class="hljs-title">number</span>&gt;(<span class="hljs-params">n: PositiveInteger&lt;T&gt;</span>) </span>{ <span class="hljs-comment">/***/</span> }</code></pre><p>The function's argument will be correctly checked at compile time, as shown here:</p><p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1675201754238/7d7f19a3-7a38-4e6b-8ba3-47068fa99c55.png?width=399" alt /></p><p>Things that can't be checked at compile time, such as variables, are passed through. This is obviously unsolvable without resorting to runtime checks (type predicates), because TypeScript types don't exist at runtime.</p><p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1675202197633/82e00a8a-2337-4231-8fed-e3e77df0bfba.png?width=516" alt /></p><p>Similarly, a non-negative integer type, aka <code>unsigned int</code>, can be defined as follows:</p><pre><code class="lang-typescript"><span class="hljs-keyword">type</span> NonnegativeInteger&lt;T <span class="hljs-keyword">extends</span> <span class="hljs-built_in">number</span>&gt; =  <span class="hljs-string">`<span class="hljs-subst">${T}</span>`</span> <span class="hljs-keyword">extends</span> <span class="hljs-string">`-<span class="hljs-subst">${<span class="hljs-built_in">any</span>}</span>`</span> | <span class="hljs-string">`<span class="hljs-subst">${<span class="hljs-built_in">any</span>}</span>.<span class="hljs-subst">${<span class="hljs-built_in">any</span>}</span>`</span> ? <span class="hljs-built_in">never</span> : T</code></pre><p>Both the <code>PositiveInteger</code> and <code>NonnegativeInteger</code> types will be available in the next version (0.1.10) of <a target="_blank" href="https://github.com/mvasilkov/natlib">natlib</a>. They can be found in the <code>prelude</code> module.</p><pre><code class="lang-typescript"><span class="hljs-comment">// npm i natlib</span><span class="hljs-keyword">import</span> <span class="hljs-keyword">type</span> { PositiveInteger, NonnegativeInteger } <span class="hljs-keyword">from</span> <span class="hljs-string">'./node_modules/natlib/prelude'</span></code></pre><hr /><p>Implementation note: all instances of <code>any</code> in the template literal types can be changed to <code>string</code> or even <code>infer _</code>  this shouldn't matter.</p>]]></description><link>https://mvasilkov.animuchan.net/typescript-positive-integer-type</link><guid isPermaLink="true">https://mvasilkov.animuchan.net/typescript-positive-integer-type</guid><dc:creator><![CDATA[Mark Vasilkov]]></dc:creator><pubDate>Wed, 01 Feb 2023 08:04:24 GMT</pubDate><cover_image>https://cdn.hashnode.com/res/hashnode/image/upload/v1675236951346/a5918b51-5a51-4219-83f5-2e1b435208e9.png</cover_image></item><item><title><![CDATA[System Font Stacks]]></title><description><![CDATA[<p>My preferred system font stacks are as follows:</p><pre><code class="lang-css"><span class="hljs-selector-tag">html</span>, <span class="hljs-selector-tag">button</span>, <span class="hljs-selector-tag">input</span> {  <span class="hljs-attribute">font-family</span>: -apple-system, <span class="hljs-string">'Segoe UI'</span>, <span class="hljs-string">'DejaVu Sans'</span>,    system-ui, sans-serif;}<span class="hljs-selector-tag">code</span>, <span class="hljs-selector-tag">kbd</span>, <span class="hljs-selector-tag">pre</span>, <span class="hljs-selector-tag">samp</span> {  <span class="hljs-attribute">font-family</span>: <span class="hljs-string">'SF Mono'</span>, SFMono-Regular, ui-monospace,    <span class="hljs-string">'DejaVu Sans Mono'</span>, Menlo, <span class="hljs-string">'Cascadia Mono'</span>, monospace;}</code></pre><p>(The CSS selectors are provisionary.)</p><h2 id="heading-system-fonts">System Fonts</h2><div class="hn-table"><table><thead><tr><td>Font family name</td><td>Why</td></tr></thead><tbody><tr><td><code>-apple-system</code></td><td>Apple</td></tr><tr><td><code>Segoe UI</code></td><td>Windows</td></tr><tr><td><code>DejaVu Sans</code></td><td>Linux</td></tr><tr><td><code>system-ui</code></td><td>Generic font family</td></tr><tr><td><code>sans-serif</code></td><td>Catch-all</td></tr></tbody></table></div><h2 id="heading-monospaced-fonts">Monospaced Fonts</h2><div class="hn-table"><table><thead><tr><td>Font family name</td><td>Why</td></tr></thead><tbody><tr><td><code>SF Mono</code></td><td>Apple</td></tr><tr><td><code>SFMono-Regular</code></td><td>Apple</td></tr><tr><td><code>ui-monospace</code></td><td>Apple <a target="_blank" href="https://webkit.org/blog/10247/new-webkit-features-in-safari-13-1/">Safari</a></td></tr><tr><td><code>DejaVu Sans Mono</code></td><td>Bitstream Vera Sans Mono (Linux)</td></tr><tr><td><code>Menlo</code></td><td>Bitstream Vera Sans Mono (Apple)</td></tr><tr><td><code>Cascadia Mono</code></td><td><a target="_blank" href="https://learn.microsoft.com/en-us/typography/fonts/windows_11_font_list">Windows</a></td></tr><tr><td><code>monospace</code></td><td>Generic font family</td></tr></tbody></table></div>]]></description><link>https://mvasilkov.animuchan.net/system-font-stacks</link><guid isPermaLink="true">https://mvasilkov.animuchan.net/system-font-stacks</guid><dc:creator><![CDATA[Mark Vasilkov]]></dc:creator><pubDate>Tue, 17 Jan 2023 13:12:37 GMT</pubDate><cover_image>https://cdn.hashnode.com/res/hashnode/image/stock/unsplash/p8gzCnZf39k/upload/5fadd76be7101e26f5f89734b484c8c8.jpeg</cover_image></item><item><title><![CDATA[Code scraps: linking node_modules during build]]></title><description><![CDATA[<p>Before version 0.1, <a target="_blank" href="https://github.com/mvasilkov/natlib">natlib</a> had examples in the main repo, and they were built together with the library. (This is no longer the case, as the examples and tests moved to a <a target="_blank" href="https://github.com/mvasilkov/natlab">separate repo</a>.)</p><p>To produce a node_modules folder as if natlib was installed as a dependency, the following Python script was used:</p><pre><code class="lang-python"><span class="hljs-comment">#!/usr/bin/env python3</span><span class="hljs-keyword">from</span> contextlib <span class="hljs-keyword">import</span> contextmanager, ExitStack<span class="hljs-keyword">from</span> pathlib <span class="hljs-keyword">import</span> Path<span class="hljs-keyword">import</span> shutil<span class="hljs-keyword">from</span> but.external.typescript <span class="hljs-keyword">import</span> typescript_callOUR_ROOT = Path(__file__).resolve().parents[<span class="hljs-number">1</span>]<span class="hljs-meta">@contextmanager</span><span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">create_symlink</span>(<span class="hljs-params">path: Path</span>):</span>    node_modules = path / <span class="hljs-string">'node_modules'</span>    node_modules.mkdir()    (node_modules / <span class="hljs-string">'natlib'</span>).symlink_to(OUR_ROOT / <span class="hljs-string">'out'</span>, target_is_directory=<span class="hljs-literal">True</span>)    <span class="hljs-keyword">try</span>:        <span class="hljs-keyword">yield</span>    <span class="hljs-keyword">finally</span>:        shutil.rmtree(node_modules)<span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">build</span>():</span>    examples = OUR_ROOT / <span class="hljs-string">'examples'</span>    projects = (        (<span class="hljs-string">'..'</span>, <span class="hljs-literal">False</span>),        (<span class="hljs-string">'couch2048'</span>, <span class="hljs-literal">True</span>),        (<span class="hljs-string">'notfound'</span>, <span class="hljs-literal">True</span>),    )    <span class="hljs-keyword">for</span> path_str, need_symlink <span class="hljs-keyword">in</span> projects:        path = examples / path_str        <span class="hljs-keyword">with</span> ExitStack() <span class="hljs-keyword">as</span> stack:            <span class="hljs-keyword">if</span> need_symlink:                stack.enter_context(create_symlink(path))            typescript_call([<span class="hljs-string">'--project'</span>, path])</code></pre><p>Here, <code>OUR_ROOT / 'out'</code> is where TypeScript puts the resulting JS files.</p><p>This way the code that runs inside the context manager block has its node_modules folder set up correctly.</p>]]></description><link>https://mvasilkov.animuchan.net/linking-node-modules-during-build</link><guid isPermaLink="true">https://mvasilkov.animuchan.net/linking-node-modules-during-build</guid><dc:creator><![CDATA[Mark Vasilkov]]></dc:creator><pubDate>Mon, 16 Jan 2023 11:57:01 GMT</pubDate><cover_image>https://cdn.hashnode.com/res/hashnode/image/upload/v1673870361341/4a1edda6-9219-409e-9f85-9ee0863009ef.jpeg</cover_image></item><item><title><![CDATA[Disable caret blinking in macOS]]></title><description><![CDATA[<p>Recently I began using Obsidian (1.0), it's a great little offline wiki application. What it sorely lacks, however, is an option to turn off caret blinking.</p><p>Thankfully there's the following macOS hidden setting:</p><pre><code class="lang-sh">defaults write -g NSTextInsertionPointBlinkPeriodOff -<span class="hljs-built_in">float</span> 0defaults write -g NSTextInsertionPointBlinkPeriodOn -<span class="hljs-built_in">float</span> 4000000000</code></pre><p>This worked for me on macOS Big Sur. I hope Obsidian adds this option before Apple kills the workaround, bless its little soul.</p>]]></description><link>https://mvasilkov.animuchan.net/disable-caret-blinking-in-macos</link><guid isPermaLink="true">https://mvasilkov.animuchan.net/disable-caret-blinking-in-macos</guid><dc:creator><![CDATA[Mark Vasilkov]]></dc:creator><pubDate>Wed, 19 Oct 2022 07:29:12 GMT</pubDate><cover_image>https://cdn.hashnode.com/res/hashnode/image/stock/unsplash/wB7V7mhufy4/upload/3c1f2b6d520488521da09c6af4df70b2.jpeg</cover_image></item><item><title><![CDATA[The Neatness (js13kGames–2022)]]></title><description><![CDATA[<p>This is the story of <a target="_blank" href="https://js13kgames.com/games/the-neatness/index.html">The Neatness</a>.</p><p>It all started when I began playing The Looker, a brilliant game by Bradley Lovell. Line-based puzzles in The Looker are mostly satire, the player being able to bypass many of those by scribbling madly. I was instantly convinced, however, that it was the perfect game mechanic.</p><p>The title of the game, The Neatness, is absolutely a nod to Jonathan Blow's work, which I'm a huge fan of. So this was the easiest decision ever.</p><p>Level design was done entirely on paper, which naturally worked great for this type of game mechanic. That's not to say it was an especially easy task, I found myself iterating on puzzles for the entire duration of the compo.</p><p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1663150911584/LGUlVGLsW.png" alt="Level design be like" /></p><p>And here's how the actual development went:</p><h2 id="heading-algorithms">Algorithms</h2><p>The most basic feature of The Neatness is drawing lines on screen. Of course, the first thing that comes to mind is the Bresenham's line algorithm. However, Bresenham's algo makes diagonal connections; if we're going to check the connectivity on a square grid, it makes more sense to only account for neighbors in four main directions (left, up, right, down).</p><p>Having that in mind, I implemented the digital differential analyzer (DDA) algorithm for drawing lines, and it worked flawlessly, first try. And I totally didn't have to debug it for like two hours because of a subtle bug.</p><div class="embed-wrapper"><div class="embed-loading"><div class="loadingRow"></div><div class="loadingRow"></div></div><a class="embed-card" href="https://twitter.com/mvasilkov/status/1558806405171355650">https://twitter.com/mvasilkov/status/1558806405171355650</a></div><p>The second feature I needed was the ability to tell if one control point was connected to the other, to signal level completion. And nope, I didn't go with the A* algo for that. I wanted the code to be as efficient as possible at this point, since I had no idea how computationally intensive the other parts of the game would end up being.</p><p>Instead of A*, I decided to flood fill any new lines that are drawn with the first nonzero neighboring thing they meet (either a control point, or another line). Then the code checks if we've met a different neighboring thing, meaning the two are now connected.</p><p>Despite using the flood fill algorithm, this is very cheap to compute: the player shouldn't be able to cover any significant length between pointer events, and we never update the same spot twice.</p><p>Thanks to Lode Vandevenne's excellent <a target="_blank" href="https://lodev.org/cgtutor/index.html">Computer Graphics Tutorial</a>, I was even able to implement this stuff reasonably fast.</p><h2 id="heading-colors">Colors</h2><p>Being incapable of producing a color scheme that doesn't look horribly wrong, I left this task to professionals and went with <a target="_blank" href="https://lospec.com/palette-list/blk-neo">BLK NEO</a> and <a target="_blank" href="https://lospec.com/palette-list/twilioquest-76">TwilioQuest 76</a> palettes.</p><p>However, the main set of colors didn't work on a bright background. To remedy this situation, I used the entirety of my severely limited knowledge about colors to do the following:</p><ol><li>Convert the main colors from sRGB to linear RGB</li><li>Take that into the Oklab color space</li><li>Tone down the L channel ever so slightly</li><li>Retrace the steps all the way back to sRGB</li></ol><p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1663150764471/n8T7noVtu.png" alt="Color scheme" /></p><p>I'm immensely grateful to Bjrn Ottosson, the man, the legend, who bestowed the <a target="_blank" href="https://bottosson.github.io/posts/oklab/">Oklab color space</a> upon us. It's magical.</p><h2 id="heading-pixels">Pixels</h2><p>Another decision made very early into the competition was to use non-square pixels (kind of like SNES) for the painting layer. Ideally I'd like to simulate a proper CRT screen with shaders, but that feat of engineering surpasses my WebGL abilities rather dramatically.</p><p>What I've learned about the SNES video output is as follows:</p><ul><li>Internal SNES aspect ratio is 8:7, at the resolution of 256224</li><li>Contemporary CRT TV aspect ratio was 4:3</li><li>So the pixel aspect ratio (PAR) should be 7:6 (4:3  8:7)</li><li>However PAR of an NTSC TV is 8:7 <a target="_blank" href="https://www.nesdev.org/wiki/Overscan#NTSC">because reasons</a></li></ul><p>So in the end I went with the PAR of 8:7, which gives a nice subtle anti-aliasing effect.</p><h2 id="heading-sprites">Sprites</h2><p>The Neatness uses both 1-bit and 2-bit sprites, encoded as plain bitmaps and represented using <code>Array&lt;Number&gt;</code> in JS. I don't think that implementing any form of compression (such as RLE) for these bitmaps is productive, because the js13kGames' compulsory submission format  ZIP  will outperform RLE anyway.</p><p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1668791805987/_n7Vyqwo_.png" alt="The bois" /></p><p>2-bit sprites (which amounts to transparency + 3 colors) are used for the skulls, gravestones, the princess, and her crown (the crown being a separate sprite because I wanted it to have a distinct color). The levels and the buttons on the right side of the screen are 1-bit sprites.</p><p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1663151501842/uYBuRIKe3.png" alt="The princess" /></p><p>In order to iterate on the level design somewhat efficiently, I wrote a Python encoder that takes a bunch of picture files and outputs bitmaps as JS arrays. Using Aseprite as a level editor is wonderfully convenient.</p><p>One caveat to pay attention to when working with bitmaps in JS: bitwise operations truncate their operands to 32 bits! Meaning that you can't use the entirety of JavaScript's 53-bit integers if you do shift right, either <code>&gt;&gt;</code> or <code>&gt;&gt;&gt;</code>  you have to divide by a power of 2 instead.</p><h2 id="heading-natlib">natlib</h2><p><a target="_blank" href="https://github.com/mvasilkov/natlib">natlib</a> is a highly composable library for small games, cannibalized from my previous js13kGames entries. The part that I'm especially fond of is the fixed-step game loop, I use it at every opportunity (which is not often).</p><p>The Neatness uses the following bits and pieces from natlib:</p><ul><li>2D vector math</li><li><a target="_blank" href="https://codepen.io/mvasilkov/pen/yLKEbyr">Viewport auto-scaling</a></li><li>Mouse and touch event handling</li><li>PRNG (the PRNG of choice being Mulberry32, designed by Tommy Ettinger)</li><li>The fixed-step game loop mentioned earlier</li></ul><h2 id="heading-package-size">Package size</h2><p>One of the prominent challenges of the js13kGames compo is the package size, which is required to be no larger than 13,312 bytes.</p><p>A 13 KB ZIP file is a good 7090 KB of JS. Turns out I physically cannot write that much code in a month. By the end of the compo I still had some 2 KB to spare, before any optimizations (and I maintained a list of possible code size optimizations just in case.)</p><p>By the way, <a target="_blank" href="https://github.com/mvasilkov/neatness2022">The Neatness source code</a> is released under the GNU General Public License version 3.</p><h2 id="heading-kudos">Kudos</h2><p>I'd like to thank my friends <a target="_blank" href="https://twitter.com/iiSatana">@iiSatana</a> and <a target="_blank" href="https://twitter.com/andrey_shuster">Andrey Shuster</a> for beta testing my stuff.</p><p>Super special thanks to <a target="_blank" href="https://twitter.com/end3r">Andrzej Mazur</a> for organizing this amazing event.</p><p>And thank you for reading this wall of text.</p>]]></description><link>https://mvasilkov.animuchan.net/the-neatness-js13kgames2022</link><guid isPermaLink="true">https://mvasilkov.animuchan.net/the-neatness-js13kgames2022</guid><dc:creator><![CDATA[Mark Vasilkov]]></dc:creator><pubDate>Wed, 14 Sep 2022 10:35:26 GMT</pubDate><cover_image>https://cdn.hashnode.com/res/hashnode/image/upload/v1673870448196/3427a3cb-7de6-428f-b1f3-6f6e33acc132.png</cover_image></item></channel></rss>