<?xml version="1.0" encoding="utf-8" standalone="yes"?>
<?xml-stylesheet type="text/xsl" href="/rss.xsl"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
  <channel>
    <title>Andrey Listopadov</title>
    <link>https://andreyor.st/tags/tic80/</link>
    <image>
      <title>Andrey Listopadov</title>
      <link>https://andreyor.st/tags/tic80/</link>
      <url>https://andreyor.st/tags/tic80/favicon-64.png</url>
    </image>
    <description>Posts tagged 'tic80' by Andrey Listopadov</description>
    <generator>Hugo -- gohugo.io</generator>
    <language>en-us</language>
    <copyright>Andrey Listopadov 2020-2026 - This work is licensed under a Creative Commons Attribution-NonCommercial-ShareAlike 4.0 International License</copyright>
    <lastBuildDate>Wed, 17 Jun 2026 19:14:00 +0300</lastBuildDate>
    <atom:link href="https://andreyor.st/tags/tic80/feed.xml" rel="self" type="application/rss+xml" />
    <item>
      <title>Better slopes in AABB collision systems</title>
      <link>https://andreyor.st/posts/2026-06-17-better-slopes-in-aabb-collision-systems/</link>
      <guid>https://andreyor.st/posts/2026-06-17-better-slopes-in-aabb-collision-systems/</guid>
      <description>&lt;p&gt;Earlier this year I &lt;a href=&#34;https://andreyor.st/posts/2026-01-09-slopes-in-aabb-collision-systems/&#34;&gt;published a guide&lt;/a&gt; on how to implement slopes in AABB collision resolution using &lt;a href=&#34;https://github.com/kikito/bump.lua&#34; target=&#34;_blank&#34;&gt;bump.lua&lt;/a&gt;.
The resulting system worked, but was a bit hard to use in-game and had a few issues, so I wouldn&amp;rsquo;t consider it a viable solution.&lt;/p&gt;
&lt;p&gt;The main issue with the old approach was the use of the &lt;code&gt;cross&lt;/code&gt; collision response.
It allowed the object to move through the slope as if it wasn&amp;rsquo;t there at all, and then the update loop corrected the &lt;code&gt;y&lt;/code&gt; position after the fact.
This alone makes writing code the game a lot more complicated than it needs to be:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-fennel&#34; data-lang=&#34;fennel&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;(&lt;span style=&#34;font-weight:bold;font-style:italic&#34;&gt;fn &lt;/span&gt;&lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;player-collision-filter&lt;/span&gt; [&lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;_&lt;/span&gt; &lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;obj&lt;/span&gt;]
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  (&lt;span style=&#34;font-weight:bold&#34;&gt;if &lt;/span&gt;(&lt;span style=&#34;font-weight:bold&#34;&gt;not= &lt;/span&gt;&lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;nil&lt;/span&gt; &lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;obj.slope&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      &lt;span style=&#34;color:#666;font-style:italic&#34;&gt;:cross&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      &lt;span style=&#34;color:#666;font-style:italic&#34;&gt;:slide&lt;/span&gt;))
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;(&lt;span style=&#34;font-weight:bold;font-style:italic&#34;&gt;fn &lt;/span&gt;&lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;grounded?&lt;/span&gt; [&lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;obj&lt;/span&gt;]
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  (&lt;span style=&#34;font-weight:bold&#34;&gt;let &lt;/span&gt;[(&lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;_&lt;/span&gt; &lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;y&lt;/span&gt; &lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;collisions&lt;/span&gt; &lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;l&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        (&lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;world&lt;/span&gt;&lt;span style=&#34;color:#666;font-style:italic&#34;&gt;:check&lt;/span&gt; &lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;obj&lt;/span&gt; &lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;obj.x&lt;/span&gt; (&lt;span style=&#34;font-weight:bold&#34;&gt;+ &lt;/span&gt;&lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;obj.y&lt;/span&gt; 1) &lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;player-collision-filter&lt;/span&gt;)]
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    (&lt;span style=&#34;font-weight:bold&#34;&gt;and &lt;/span&gt;(&lt;span style=&#34;font-weight:bold&#34;&gt;or &lt;/span&gt;(&lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;faccumulate&lt;/span&gt; [&lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;res&lt;/span&gt; &lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;nil&lt;/span&gt; &lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;i&lt;/span&gt; 1 &lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;l&lt;/span&gt; &lt;span style=&#34;color:#666;font-style:italic&#34;&gt;:until&lt;/span&gt; &lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;res&lt;/span&gt;]
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;               (&lt;span style=&#34;font-weight:bold&#34;&gt;let &lt;/span&gt;[&lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;col&lt;/span&gt; (&lt;span style=&#34;font-weight:bold&#34;&gt;. &lt;/span&gt;&lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;collisions&lt;/span&gt; &lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;i&lt;/span&gt;)]
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                 (&lt;span style=&#34;font-weight:bold&#34;&gt;when &lt;/span&gt;&lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;col.other.slope&lt;/span&gt; &lt;span style=&#34;color:#888;font-style:italic&#34;&gt;;; &amp;lt;- eeeeeh...&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                   (&lt;span style=&#34;font-weight:bold&#34;&gt;= &lt;/span&gt;&lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;obj.y&lt;/span&gt; (&lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;col.other.slope&lt;/span&gt; &lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;obj&lt;/span&gt;)))))
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;             (&lt;span style=&#34;font-weight:bold&#34;&gt;= &lt;/span&gt;&lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;obj.y&lt;/span&gt; &lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;y&lt;/span&gt;))
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;         &lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;collisions&lt;/span&gt;)))
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;(&lt;span style=&#34;font-weight:bold;font-style:italic&#34;&gt;fn &lt;/span&gt;&lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;update&lt;/span&gt; [&lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;player&lt;/span&gt; &lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;main&lt;/span&gt;]
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#888;font-style:italic&#34;&gt;;; ---8&amp;lt;---&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  (&lt;span style=&#34;font-weight:bold&#34;&gt;let &lt;/span&gt;[(&lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;x&lt;/span&gt; &lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;y&lt;/span&gt; &lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;collisions&lt;/span&gt; &lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;l&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        (&lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;world&lt;/span&gt;&lt;span style=&#34;color:#666;font-style:italic&#34;&gt;:move&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;         &lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;player&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;         (&lt;span style=&#34;font-weight:bold&#34;&gt;+ &lt;/span&gt;&lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;player.x&lt;/span&gt; &lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;player.x-vel&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;         (&lt;span style=&#34;font-weight:bold&#34;&gt;+ &lt;/span&gt;&lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;player.y&lt;/span&gt; &lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;player.y-vel&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;         &lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;player-collision-filter&lt;/span&gt;)]
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    (&lt;span style=&#34;font-weight:bold&#34;&gt;doto &lt;/span&gt;&lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;player&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      (&lt;span style=&#34;font-weight:bold&#34;&gt;tset &lt;/span&gt;&lt;span style=&#34;color:#666;font-style:italic&#34;&gt;:x&lt;/span&gt; &lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;x&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      (&lt;span style=&#34;font-weight:bold&#34;&gt;tset &lt;/span&gt;&lt;span style=&#34;color:#666;font-style:italic&#34;&gt;:y&lt;/span&gt; &lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;y&lt;/span&gt;))
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    (&lt;span style=&#34;font-weight:bold&#34;&gt;for &lt;/span&gt;[&lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;i&lt;/span&gt; 1 &lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;l&lt;/span&gt;]
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      (&lt;span style=&#34;font-weight:bold&#34;&gt;let &lt;/span&gt;[&lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;collision&lt;/span&gt; (&lt;span style=&#34;font-weight:bold&#34;&gt;. &lt;/span&gt;&lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;collisions&lt;/span&gt; &lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;i&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            &lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;other&lt;/span&gt; &lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;collision.other&lt;/span&gt;]
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        (&lt;span style=&#34;font-weight:bold&#34;&gt;when &lt;/span&gt;&lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;other.slope&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;          (&lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;case&lt;/span&gt; (&lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;other.slope&lt;/span&gt; &lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;player&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            &lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;y*&lt;/span&gt; (&lt;span style=&#34;font-weight:bold&#34;&gt;when &lt;/span&gt;(&lt;span style=&#34;font-weight:bold&#34;&gt;&amp;gt;= &lt;/span&gt;&lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;player.y&lt;/span&gt; &lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;y*&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                 (&lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;world&lt;/span&gt;&lt;span style=&#34;color:#666;font-style:italic&#34;&gt;:update&lt;/span&gt; &lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;player&lt;/span&gt; &lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;player.x&lt;/span&gt; &lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;y*&lt;/span&gt; &lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;player.w&lt;/span&gt; &lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;player.h&lt;/span&gt;) &lt;span style=&#34;color:#888;font-style:italic&#34;&gt;;; &amp;lt;- yikes!&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                 (&lt;span style=&#34;font-weight:bold&#34;&gt;doto &lt;/span&gt;&lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;player&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                   (&lt;span style=&#34;font-weight:bold&#34;&gt;tset &lt;/span&gt;&lt;span style=&#34;color:#666;font-style:italic&#34;&gt;:y&lt;/span&gt; &lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;y*&lt;/span&gt;)))))))
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#888;font-style:italic&#34;&gt;;; ---8&amp;lt;---&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    ))
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;I had to resort to calling the &lt;code&gt;world:update&lt;/code&gt; method, which, as &lt;a href=&#34;https://github.com/kikito/bump.lua#changing-the-position-and-dimensions-of-items-in-the-world&#34; target=&#34;_blank&#34;&gt;described in the bump&amp;rsquo;s docs&lt;/a&gt;, is used to update object&amp;rsquo;s position in the world without checking collisions.
This made slopes really awkward to use and work with.
And while I suspected that this was a wrong approach, I decided to go with it anyway, because at the time I didn&amp;rsquo;t knew better.&lt;/p&gt;
&lt;p&gt;Then, while &lt;a href=&#34;https://andreyor.st/posts/2026-05-24-spring-lisp-game-jam-2026/&#34;&gt;working&lt;/a&gt; on the &lt;a href=&#34;https://tic80.com/play?cart=4708&#34; target=&#34;_blank&#34;&gt;game&lt;/a&gt; for the &lt;a href=&#34;https://itch.io/jam/spring-lisp-game-jam-2026&#34; target=&#34;_blank&#34;&gt;Spring Lisp Game Jam&lt;/a&gt; I again needed a custom collision handling that is not supported by &lt;code&gt;bump.lua&lt;/code&gt; directly, but a bit simpler this time around.
It&amp;rsquo;s a common thing in games, where you can pass through the block from one side, but not from the other.
Like clouds usually behave in platformers - you can cross them from below, but still land on them:&lt;/p&gt;
&lt;figure&gt;
&lt;p&gt;&lt;video autoplay loop muted&gt;&lt;source src=&#34;https://andreyor.st/2026-06-17-better-slopes-in-aabb-collision-systems/cloud-platform.mp4&#34; type=&#34;video/mp4&#34;&gt;
Your browser does not support the video tag.&lt;/video&gt;&lt;/p&gt;
&lt;figcaption&gt;
  &lt;p&gt;A cloud platform in the game Rayman (1995)&lt;/p&gt;
&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;However, &lt;code&gt;bump.lua&lt;/code&gt; by default only supports the following type of collisions: &lt;code&gt;slide&lt;/code&gt;, &lt;code&gt;cross&lt;/code&gt;, &lt;code&gt;touch&lt;/code&gt;, and &lt;code&gt;bounce&lt;/code&gt;:&lt;/p&gt;
&lt;figure&gt;&lt;img src=&#34;https://andreyor.st/2026-06-17-better-slopes-in-aabb-collision-systems/collision-responses.png&#34;
         alt=&#34;Figure 1: Response types from bump.lua documentation&#34;/&gt;&lt;figcaption&gt;
            &lt;p&gt;&lt;span class=&#34;figure-number&#34;&gt;Figure 1: &lt;/span&gt;Response types from bump.lua &lt;a href=&#34;https://github.com/kikito/bump.lua#collision-resolution&#34; target=&#34;_blank&#34;&gt;documentation&lt;/a&gt;&lt;/p&gt;
        &lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p&gt;Solving this using the &lt;code&gt;cross&lt;/code&gt; handler seemed wrong, and after a bit of searching I learned that in &lt;code&gt;bump.lua&lt;/code&gt; you can define your own collision response functions.
So I defined my own collision response called &lt;code&gt;slide-cross&lt;/code&gt; (because the main intention was to be able to go through from the sides with the &lt;code&gt;slide&lt;/code&gt; response but cross over it from below):&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-fennel&#34; data-lang=&#34;fennel&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;(&lt;span style=&#34;font-weight:bold;font-style:italic&#34;&gt;fn &lt;/span&gt;&lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;slide-cross&lt;/span&gt; [&lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;world&lt;/span&gt; &lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;col&lt;/span&gt; &lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;x&lt;/span&gt; &lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;y&lt;/span&gt; &lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;w&lt;/span&gt; &lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;h&lt;/span&gt; &lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;goal-x&lt;/span&gt; &lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;goal-y&lt;/span&gt; &lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;filter&lt;/span&gt;]
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  (&lt;span style=&#34;font-weight:bold&#34;&gt;let &lt;/span&gt;[&lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;goal-x&lt;/span&gt; (&lt;span style=&#34;font-weight:bold&#34;&gt;or &lt;/span&gt;&lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;goal-x&lt;/span&gt; &lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;x&lt;/span&gt;)]
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    (&lt;span style=&#34;font-weight:bold;font-style:italic&#34;&gt;var &lt;/span&gt;&lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;goal-y&lt;/span&gt; (&lt;span style=&#34;font-weight:bold&#34;&gt;or &lt;/span&gt;&lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;goal-y&lt;/span&gt; &lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;y&lt;/span&gt;))
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    (&lt;span style=&#34;font-weight:bold&#34;&gt;if &lt;/span&gt;(&lt;span style=&#34;font-weight:bold&#34;&gt;&amp;lt;= &lt;/span&gt;(&lt;span style=&#34;font-weight:bold&#34;&gt;+ &lt;/span&gt;&lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;y&lt;/span&gt; &lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;h&lt;/span&gt;) &lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;col.otherRect.y&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        (&lt;span style=&#34;font-weight:bold&#34;&gt;let &lt;/span&gt;[&lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;tch&lt;/span&gt; &lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;col.touch&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;              &lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;move&lt;/span&gt; &lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;col.move&lt;/span&gt;]
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;          (&lt;span style=&#34;font-weight:bold&#34;&gt;when &lt;/span&gt;(&lt;span style=&#34;font-weight:bold&#34;&gt;or &lt;/span&gt;(&lt;span style=&#34;font-weight:bold&#34;&gt;not= &lt;/span&gt;&lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;move.x&lt;/span&gt; 0) (&lt;span style=&#34;font-weight:bold&#34;&gt;not= &lt;/span&gt;&lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;move.y&lt;/span&gt; 0))
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            (&lt;span style=&#34;font-weight:bold&#34;&gt;set &lt;/span&gt;&lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;goal-y&lt;/span&gt; &lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;tch.y&lt;/span&gt;))
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;          (&lt;span style=&#34;font-weight:bold&#34;&gt;set &lt;/span&gt;&lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;col.slide&lt;/span&gt; {&lt;span style=&#34;color:#666;font-style:italic&#34;&gt;:x&lt;/span&gt; &lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;goal-x&lt;/span&gt; &lt;span style=&#34;color:#666;font-style:italic&#34;&gt;:y&lt;/span&gt; &lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;goal-y&lt;/span&gt;})
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;          (&lt;span style=&#34;font-weight:bold&#34;&gt;let &lt;/span&gt;[(&lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;cols&lt;/span&gt; &lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;len&lt;/span&gt;) (&lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;world&lt;/span&gt;&lt;span style=&#34;color:#666;font-style:italic&#34;&gt;:project&lt;/span&gt; &lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;col.item&lt;/span&gt; &lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;tch.x&lt;/span&gt; &lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;tch.y&lt;/span&gt; &lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;w&lt;/span&gt; &lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;h&lt;/span&gt; &lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;goal-x&lt;/span&gt; &lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;goal-y&lt;/span&gt; &lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;filter&lt;/span&gt;)]
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            (&lt;span style=&#34;font-weight:bold&#34;&gt;values &lt;/span&gt;&lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;goal-x&lt;/span&gt; &lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;goal-y&lt;/span&gt; &lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;cols&lt;/span&gt; &lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;len&lt;/span&gt;)))
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        (&lt;span style=&#34;font-weight:bold&#34;&gt;let &lt;/span&gt;[(&lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;cols&lt;/span&gt; &lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;len&lt;/span&gt;) (&lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;world&lt;/span&gt;&lt;span style=&#34;color:#666;font-style:italic&#34;&gt;:project&lt;/span&gt; &lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;col.item&lt;/span&gt; &lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;x&lt;/span&gt; &lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;y&lt;/span&gt; &lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;w&lt;/span&gt; &lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;h&lt;/span&gt; &lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;goal-x&lt;/span&gt; &lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;goal-y&lt;/span&gt; &lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;filter&lt;/span&gt;)]
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;          (&lt;span style=&#34;font-weight:bold&#34;&gt;values &lt;/span&gt;&lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;goal-x&lt;/span&gt; &lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;goal-y&lt;/span&gt; &lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;cols&lt;/span&gt; &lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;len&lt;/span&gt;)))))
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;(&lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;world&lt;/span&gt;&lt;span style=&#34;color:#666;font-style:italic&#34;&gt;:addResponse&lt;/span&gt; &lt;span style=&#34;color:#666;font-style:italic&#34;&gt;:slide-cross&lt;/span&gt; &lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;slide-cross&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;With such colliders in place, I could replicate cloud platform behavior:&lt;/p&gt;
&lt;p&gt;&lt;video autoplay loop muted&gt;&lt;source src=&#34;https://andreyor.st/2026-06-17-better-slopes-in-aabb-collision-systems/bump-cloud.mp4&#34; type=&#34;video/mp4&#34;&gt;
Your browser does not support the video tag.&lt;/video&gt;&lt;/p&gt;
&lt;p&gt;I didn&amp;rsquo;t use clouds themselves in the actual game - instead, this collider was assigned to specific blocks I wanted to be able to run past, acting more as background structures you can still jump on, i.e. pedestals, but the idea of making a custom collider got me thinking.
If I can make a custom collision response function, why not try to do it for slopes too?&lt;/p&gt;
&lt;p&gt;So that&amp;rsquo;s what I did.
But before that, I had to redo slopes a bit.&lt;/p&gt;
&lt;h2 id=&#34;slopes-in-bump-dot-lua&#34;&gt;Slopes in &lt;code&gt;bump.lua&lt;/code&gt;&lt;/h2&gt;
&lt;p&gt;First, let&amp;rsquo;s start with how slopes are defined:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-fennel&#34; data-lang=&#34;fennel&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;(&lt;span style=&#34;font-weight:bold;font-style:italic&#34;&gt;fn &lt;/span&gt;&lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;make-slope&lt;/span&gt; [&lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;x&lt;/span&gt; &lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;y&lt;/span&gt; &lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;x0&lt;/span&gt; &lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;y0&lt;/span&gt; &lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;x1&lt;/span&gt; &lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;y1&lt;/span&gt; &lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;slope-type&lt;/span&gt;]
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  (&lt;span style=&#34;font-weight:bold&#34;&gt;let &lt;/span&gt;[(&lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;nx&lt;/span&gt; &lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;ny&lt;/span&gt;) (&lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;slope-normal&lt;/span&gt; &lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;x0&lt;/span&gt; &lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;y0&lt;/span&gt; &lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;x1&lt;/span&gt; &lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;y1&lt;/span&gt; &lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;slope-type&lt;/span&gt;)]
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    {&lt;span style=&#34;color:#666;font-style:italic&#34;&gt;:slope&lt;/span&gt; &lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;slope-type&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;     &lt;span style=&#34;color:#666;font-style:italic&#34;&gt;:surface&lt;/span&gt; #(&lt;span style=&#34;font-weight:bold&#34;&gt;+ &lt;/span&gt;&lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;y0&lt;/span&gt; (&lt;span style=&#34;font-weight:bold&#34;&gt;/ &lt;/span&gt;(&lt;span style=&#34;font-weight:bold&#34;&gt;* &lt;/span&gt;(&lt;span style=&#34;font-weight:bold&#34;&gt;- &lt;/span&gt;&lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;y1&lt;/span&gt; &lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;y0&lt;/span&gt;) (&lt;span style=&#34;font-weight:bold&#34;&gt;- &lt;/span&gt;(&lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;clamp&lt;/span&gt; &lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;x0&lt;/span&gt; &lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;$&lt;/span&gt; &lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;x1&lt;/span&gt;) &lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;x0&lt;/span&gt;)) (&lt;span style=&#34;font-weight:bold&#34;&gt;- &lt;/span&gt;&lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;x1&lt;/span&gt; &lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;x0&lt;/span&gt;)))
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;     &lt;span style=&#34;font-weight:bold&#34;&gt;: &lt;/span&gt;&lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;x&lt;/span&gt; &lt;span style=&#34;font-weight:bold&#34;&gt;: &lt;/span&gt;&lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;y&lt;/span&gt; &lt;span style=&#34;font-weight:bold&#34;&gt;: &lt;/span&gt;&lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;nx&lt;/span&gt; &lt;span style=&#34;font-weight:bold&#34;&gt;: &lt;/span&gt;&lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;ny&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;     &lt;span style=&#34;color:#666;font-style:italic&#34;&gt;:h&lt;/span&gt; (&lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;m/max&lt;/span&gt; &lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;y0&lt;/span&gt; &lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;y1&lt;/span&gt;) &lt;span style=&#34;color:#666;font-style:italic&#34;&gt;:w&lt;/span&gt; (&lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;m/max&lt;/span&gt; &lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;x0&lt;/span&gt; &lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;x1&lt;/span&gt;)}))
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;For example, to define a slope at coordinates &lt;code&gt;x,y&lt;/code&gt; that is &lt;code&gt;16px&lt;/code&gt; long and &lt;code&gt;8px&lt;/code&gt; tall (&lt;img class=&#34;pixelart invertable&#34; src=&#34;https://andreyor.st/2026-06-17-better-slopes-in-aabb-collision-systems/slope1.png&#34;&gt;&lt;/img&gt;), we can call &lt;code&gt;make-slope&lt;/code&gt; like this:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-fennel&#34; data-lang=&#34;fennel&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;(&lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;make-slope&lt;/span&gt; &lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;x&lt;/span&gt; &lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;y&lt;/span&gt; 0 8 16 0 &lt;span style=&#34;color:#666;font-style:italic&#34;&gt;:floor&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;A 45° &lt;code&gt;8x8&lt;/code&gt; slope (&lt;img class=&#34;pixelart invertable&#34; src=&#34;https://andreyor.st/2026-06-17-better-slopes-in-aabb-collision-systems/slope-45.png&#34;&gt;&lt;/img&gt;) can be defined as follows:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-fennel&#34; data-lang=&#34;fennel&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;(&lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;make-slope&lt;/span&gt; &lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;x&lt;/span&gt; &lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;y&lt;/span&gt; 0 8 8 0 &lt;span style=&#34;color:#666;font-style:italic&#34;&gt;:floor&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;These slopes get taller from left to right, if we want a slope that goes lower from left to right (&lt;img class=&#34;pixelart invertable&#34; src=&#34;https://andreyor.st/2026-06-17-better-slopes-in-aabb-collision-systems/slope2.png&#34;&gt;&lt;/img&gt;), we swap the coordinates:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-fennel&#34; data-lang=&#34;fennel&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;(&lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;make-slope&lt;/span&gt; &lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;x&lt;/span&gt; &lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;y&lt;/span&gt; 0 0 16 8 &lt;span style=&#34;color:#666;font-style:italic&#34;&gt;:floor&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;This function returns an object representing the slope.
Of course, we&amp;rsquo;re not limited to defining &lt;code&gt;16x8&lt;/code&gt; and &lt;code&gt;8x8&lt;/code&gt; slopes - these are just the most logical choices for a platformer in TIC-80.
We can define steeper slopes like &lt;code&gt;8x16&lt;/code&gt;, or use an arbitrary size altogether.
As long as it can be clearly represented by the underlying spritework given resolution constraints you work in.&lt;/p&gt;
&lt;p&gt;The &lt;code&gt;make-slope&lt;/code&gt; constructor computes the slope&amp;rsquo;s normal vector:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-fennel&#34; data-lang=&#34;fennel&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;(&lt;span style=&#34;font-weight:bold;font-style:italic&#34;&gt;fn &lt;/span&gt;&lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;slope-normal&lt;/span&gt; [&lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;x0&lt;/span&gt; &lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;y0&lt;/span&gt; &lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;x1&lt;/span&gt; &lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;y1&lt;/span&gt; &lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;slope-type&lt;/span&gt;]
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  (&lt;span style=&#34;font-weight:bold&#34;&gt;let &lt;/span&gt;[&lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;dx&lt;/span&gt; (&lt;span style=&#34;font-weight:bold&#34;&gt;- &lt;/span&gt;&lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;x1&lt;/span&gt; &lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;x0&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;dy&lt;/span&gt; (&lt;span style=&#34;font-weight:bold&#34;&gt;- &lt;/span&gt;&lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;y1&lt;/span&gt; &lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;y0&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;len&lt;/span&gt; (&lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;m/sqrt&lt;/span&gt; (&lt;span style=&#34;font-weight:bold&#34;&gt;+ &lt;/span&gt;(&lt;span style=&#34;font-weight:bold&#34;&gt;* &lt;/span&gt;&lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;dx&lt;/span&gt; &lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;dx&lt;/span&gt;) (&lt;span style=&#34;font-weight:bold&#34;&gt;* &lt;/span&gt;&lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;dy&lt;/span&gt; &lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;dy&lt;/span&gt;)))
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;nx&lt;/span&gt; (&lt;span style=&#34;font-weight:bold&#34;&gt;/ &lt;/span&gt;(&lt;span style=&#34;font-weight:bold&#34;&gt;- &lt;/span&gt;&lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;dy&lt;/span&gt;) &lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;len&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;ny&lt;/span&gt; (&lt;span style=&#34;font-weight:bold&#34;&gt;/ &lt;/span&gt;&lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;dx&lt;/span&gt; &lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;len&lt;/span&gt;)]
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    (&lt;span style=&#34;font-weight:bold&#34;&gt;if &lt;/span&gt;(&lt;span style=&#34;font-weight:bold&#34;&gt;or &lt;/span&gt;(&lt;span style=&#34;font-weight:bold&#34;&gt;and &lt;/span&gt;(&lt;span style=&#34;font-weight:bold&#34;&gt;= &lt;/span&gt;&lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;slope-type&lt;/span&gt; &lt;span style=&#34;color:#666;font-style:italic&#34;&gt;:floor&lt;/span&gt;) (&lt;span style=&#34;font-weight:bold&#34;&gt;&amp;gt; &lt;/span&gt;&lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;ny&lt;/span&gt; 0))
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            (&lt;span style=&#34;font-weight:bold&#34;&gt;and &lt;/span&gt;(&lt;span style=&#34;font-weight:bold&#34;&gt;= &lt;/span&gt;&lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;slope-type&lt;/span&gt; &lt;span style=&#34;color:#666;font-style:italic&#34;&gt;:ceiling&lt;/span&gt;) (&lt;span style=&#34;font-weight:bold&#34;&gt;&amp;lt; &lt;/span&gt;&lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;ny&lt;/span&gt; 0)))
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        (&lt;span style=&#34;font-weight:bold&#34;&gt;values &lt;/span&gt;(&lt;span style=&#34;font-weight:bold&#34;&gt;- &lt;/span&gt;&lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;nx&lt;/span&gt;) (&lt;span style=&#34;font-weight:bold&#34;&gt;- &lt;/span&gt;&lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;ny&lt;/span&gt;))
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        (&lt;span style=&#34;font-weight:bold&#34;&gt;values &lt;/span&gt;&lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;nx&lt;/span&gt; &lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;ny&lt;/span&gt;))))
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;It will be used in our response function to implement bouncing off the slope surface correctly - one of the things I fell short while making the previous version.&lt;/p&gt;
&lt;h2 id=&#34;slope-response-function-for-bump-dot-lua&#34;&gt;Slope response function for &lt;code&gt;bump.lua&lt;/code&gt;&lt;/h2&gt;
&lt;p&gt;Now we can define the response function itself:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-fennel&#34; data-lang=&#34;fennel&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;(&lt;span style=&#34;font-weight:bold;font-style:italic&#34;&gt;fn &lt;/span&gt;&lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;slope-response&lt;/span&gt; [&lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;world&lt;/span&gt; &lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;col&lt;/span&gt; &lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;x&lt;/span&gt; &lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;y&lt;/span&gt; &lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;w&lt;/span&gt; &lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;h&lt;/span&gt; &lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;goal-x&lt;/span&gt; &lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;goal-y&lt;/span&gt; &lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;filter&lt;/span&gt;]
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  (&lt;span style=&#34;font-weight:bold&#34;&gt;let &lt;/span&gt;[&lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;goal-x&lt;/span&gt; (&lt;span style=&#34;font-weight:bold&#34;&gt;or &lt;/span&gt;&lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;goal-x&lt;/span&gt; &lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;x&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        {&lt;span style=&#34;font-weight:bold&#34;&gt;: &lt;/span&gt;&lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;surface&lt;/span&gt; &lt;span style=&#34;color:#666;font-style:italic&#34;&gt;:slope&lt;/span&gt; &lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;slope-type&lt;/span&gt; &lt;span style=&#34;font-weight:bold&#34;&gt;: &lt;/span&gt;&lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;nx&lt;/span&gt; &lt;span style=&#34;font-weight:bold&#34;&gt;: &lt;/span&gt;&lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;ny&lt;/span&gt;} &lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;col.other&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        {&lt;span style=&#34;color:#666;font-style:italic&#34;&gt;:x&lt;/span&gt; &lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;ox&lt;/span&gt; &lt;span style=&#34;color:#666;font-style:italic&#34;&gt;:y&lt;/span&gt; &lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;oy&lt;/span&gt;} &lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;col.otherRect&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        {&lt;span style=&#34;color:#666;font-style:italic&#34;&gt;:y&lt;/span&gt; &lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;touch-y&lt;/span&gt;} &lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;col.touch&lt;/span&gt;]
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    (&lt;span style=&#34;font-weight:bold;font-style:italic&#34;&gt;var &lt;/span&gt;&lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;goal-y&lt;/span&gt; (&lt;span style=&#34;font-weight:bold&#34;&gt;or &lt;/span&gt;&lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;goal-y&lt;/span&gt; &lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;y&lt;/span&gt;))
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    (&lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;case&lt;/span&gt; &lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;slope-type&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      &lt;span style=&#34;color:#666;font-style:italic&#34;&gt;:floor&lt;/span&gt; (&lt;span style=&#34;font-weight:bold&#34;&gt;let &lt;/span&gt;[&lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;surf-y&lt;/span&gt; (&lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;m/min&lt;/span&gt; (&lt;span style=&#34;font-weight:bold&#34;&gt;+ &lt;/span&gt;&lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;oy&lt;/span&gt; (&lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;surface&lt;/span&gt; (&lt;span style=&#34;font-weight:bold&#34;&gt;- &lt;/span&gt;&lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;goal-x&lt;/span&gt; &lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;ox&lt;/span&gt;)))
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                                 (&lt;span style=&#34;font-weight:bold&#34;&gt;+ &lt;/span&gt;&lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;oy&lt;/span&gt; (&lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;surface&lt;/span&gt; (&lt;span style=&#34;font-weight:bold&#34;&gt;- &lt;/span&gt;(&lt;span style=&#34;font-weight:bold&#34;&gt;+ &lt;/span&gt;&lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;goal-x&lt;/span&gt; &lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;w&lt;/span&gt;) &lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;ox&lt;/span&gt;))))]
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;               (&lt;span style=&#34;font-weight:bold&#34;&gt;if &lt;/span&gt;(&lt;span style=&#34;font-weight:bold&#34;&gt;&amp;gt; &lt;/span&gt;(&lt;span style=&#34;font-weight:bold&#34;&gt;+ &lt;/span&gt;&lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;goal-y&lt;/span&gt; &lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;h&lt;/span&gt;) &lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;surf-y&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                   (&lt;span style=&#34;font-weight:bold&#34;&gt;if &lt;/span&gt;(&lt;span style=&#34;font-weight:bold&#34;&gt;&amp;gt; &lt;/span&gt;&lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;col.normal.y&lt;/span&gt; 0)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                       (&lt;span style=&#34;font-weight:bold&#34;&gt;set &lt;/span&gt;&lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;goal-y&lt;/span&gt; &lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;touch-y&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                       (&lt;span style=&#34;font-weight:bold&#34;&gt;set &lt;/span&gt;[&lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;goal-y&lt;/span&gt; &lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;col.normal.x&lt;/span&gt; &lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;col.normal.y&lt;/span&gt;] [(&lt;span style=&#34;font-weight:bold&#34;&gt;- &lt;/span&gt;&lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;surf-y&lt;/span&gt; &lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;h&lt;/span&gt;) &lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;nx&lt;/span&gt; &lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;ny&lt;/span&gt;]))
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                   (&lt;span style=&#34;font-weight:bold&#34;&gt;set &lt;/span&gt;[&lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;col.normal.x&lt;/span&gt; &lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;col.normal.y&lt;/span&gt;] [0 0])))
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      &lt;span style=&#34;color:#666;font-style:italic&#34;&gt;:ceiling&lt;/span&gt; (&lt;span style=&#34;font-weight:bold&#34;&gt;let &lt;/span&gt;[&lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;surf-y&lt;/span&gt; (&lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;m/max&lt;/span&gt; (&lt;span style=&#34;font-weight:bold&#34;&gt;+ &lt;/span&gt;&lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;oy&lt;/span&gt; (&lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;surface&lt;/span&gt; (&lt;span style=&#34;font-weight:bold&#34;&gt;- &lt;/span&gt;&lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;goal-x&lt;/span&gt; &lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;ox&lt;/span&gt;)))
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                                   (&lt;span style=&#34;font-weight:bold&#34;&gt;+ &lt;/span&gt;&lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;oy&lt;/span&gt; (&lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;surface&lt;/span&gt; (&lt;span style=&#34;font-weight:bold&#34;&gt;- &lt;/span&gt;(&lt;span style=&#34;font-weight:bold&#34;&gt;+ &lt;/span&gt;&lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;goal-x&lt;/span&gt; &lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;w&lt;/span&gt;) &lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;ox&lt;/span&gt;))))]
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                 (&lt;span style=&#34;font-weight:bold&#34;&gt;if &lt;/span&gt;(&lt;span style=&#34;font-weight:bold&#34;&gt;&amp;lt; &lt;/span&gt;&lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;goal-y&lt;/span&gt; &lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;surf-y&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                     (&lt;span style=&#34;font-weight:bold&#34;&gt;if &lt;/span&gt;(&lt;span style=&#34;font-weight:bold&#34;&gt;&amp;lt; &lt;/span&gt;&lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;col.normal.y&lt;/span&gt; 0)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                         (&lt;span style=&#34;font-weight:bold&#34;&gt;set &lt;/span&gt;&lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;goal-y&lt;/span&gt; &lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;touch-y&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                         (&lt;span style=&#34;font-weight:bold&#34;&gt;set &lt;/span&gt;[&lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;goal-y&lt;/span&gt; &lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;col.normal.x&lt;/span&gt; &lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;col.normal.y&lt;/span&gt;] [&lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;surf-y&lt;/span&gt; &lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;nx&lt;/span&gt; &lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;ny&lt;/span&gt;]))
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                     (&lt;span style=&#34;font-weight:bold&#34;&gt;set &lt;/span&gt;[&lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;col.normal.x&lt;/span&gt; &lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;col.normal.y&lt;/span&gt;] [0 0])))
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      &lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;_&lt;/span&gt; (&lt;span style=&#34;font-weight:bold;font-style:italic&#34;&gt;error &lt;/span&gt;(&lt;span style=&#34;font-weight:bold&#34;&gt;.. &lt;/span&gt;&lt;span style=&#34;color:#666;font-style:italic&#34;&gt;&amp;#34;unknown slope type: &amp;#34;&lt;/span&gt; (&lt;span style=&#34;font-weight:bold;font-style:italic&#34;&gt;tostring &lt;/span&gt;&lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;_&lt;/span&gt;))))
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    (&lt;span style=&#34;font-weight:bold&#34;&gt;set &lt;/span&gt;&lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;col.slide&lt;/span&gt; {&lt;span style=&#34;color:#666;font-style:italic&#34;&gt;:x&lt;/span&gt; &lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;goal-x&lt;/span&gt; &lt;span style=&#34;color:#666;font-style:italic&#34;&gt;:y&lt;/span&gt; &lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;goal-y&lt;/span&gt;})
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    (&lt;span style=&#34;font-weight:bold;font-style:italic&#34;&gt;local &lt;/span&gt;(&lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;cols&lt;/span&gt; &lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;len&lt;/span&gt;) (&lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;world&lt;/span&gt;&lt;span style=&#34;color:#666;font-style:italic&#34;&gt;:project&lt;/span&gt; &lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;col.item&lt;/span&gt; &lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;x&lt;/span&gt; &lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;y&lt;/span&gt; &lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;w&lt;/span&gt; &lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;h&lt;/span&gt; &lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;goal-x&lt;/span&gt; &lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;goal-y&lt;/span&gt; &lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;filter&lt;/span&gt;))
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    (&lt;span style=&#34;font-weight:bold&#34;&gt;values &lt;/span&gt;&lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;goal-x&lt;/span&gt; &lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;goal-y&lt;/span&gt; &lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;cols&lt;/span&gt; &lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;len&lt;/span&gt;)))
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;It has two &lt;code&gt;case&lt;/code&gt; branches - one that I&amp;rsquo;ve omitted in the above explanation of &lt;code&gt;make-slope&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;The &lt;code&gt;slope-type&lt;/code&gt; argument, set to &lt;code&gt;floor&lt;/code&gt; in those examples, determines if a slope is a ground one or a one on the ceiling.
Another thing that wasn&amp;rsquo;t supported in the old method.&lt;/p&gt;
&lt;p&gt;This response function works as follows:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;It gets &lt;code&gt;goal-x&lt;/code&gt; and &lt;code&gt;goal-y&lt;/code&gt;, as well as other usual parameters, and the collision object.&lt;/li&gt;
&lt;li&gt;The slope is always in &lt;code&gt;col.other&lt;/code&gt;, because slope itself never collides with anything (they&amp;rsquo;re never passed to &lt;code&gt;world:move&lt;/code&gt; calls).&lt;/li&gt;
&lt;li&gt;We get the slope&amp;rsquo;s rectangle to adjust our object&amp;rsquo;s position across the slope&amp;rsquo;s width.&lt;/li&gt;
&lt;li&gt;Then we check &lt;code&gt;slope-type&lt;/code&gt;
&lt;ul&gt;
&lt;li&gt;For floor slopes we get the &lt;code&gt;min&lt;/code&gt; of the &lt;code&gt;y&lt;/code&gt; coordinate of the slope &lt;code&gt;y&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Then we check if the &lt;code&gt;goal-y&lt;/code&gt; received by the collision response function is below the slope&amp;rsquo;s surface.&lt;/li&gt;
&lt;li&gt;If so, and the normal vector points up, meaning the collision comes from above the slope, we set the &lt;code&gt;goal-y&lt;/code&gt; and normal vector.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;For ceiling slopes, the logic is the same, only some signs and checks are inverted.&lt;/li&gt;
&lt;li&gt;If approached from the other side of the slope, i.e. from below - the collision acts like a regular AABB, so the slope forms a right triangle.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Handling &lt;code&gt;goal-y&lt;/code&gt; and normal vector inside the response function provides much cleaner slopes:&lt;/p&gt;
&lt;figure&gt;
&lt;p&gt;&lt;video autoplay loop muted&gt;&lt;source src=&#34;https://andreyor.st/2026-06-17-better-slopes-in-aabb-collision-systems/aabb-slopes.mp4&#34; type=&#34;video/mp4&#34;&gt;
Your browser does not support the video tag.&lt;/video&gt;&lt;/p&gt;
&lt;figcaption&gt;
  &lt;p&gt;Note: it may seem that balls float over the slopes without touching, but that&#39;s only a visual quirk because ball&#39;s collider is still a 8x8 square - it touches the slope wiht a corner, but the sprite makes it look that it&#39;s floating.  A more precise collider for the ball is needed, but it&#39;s out of the scope of this post.&lt;/p&gt;
&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;The old implementation couldn&amp;rsquo;t achieve such smooth gliding over the slope&amp;rsquo;s surface.
Balls get stuck on adjacent slopes, and incorrect normals prevent balls from rolling off smoothly:&lt;/p&gt;
&lt;figure&gt;
&lt;p&gt;&lt;video autoplay loop muted&gt;&lt;source src=&#34;https://andreyor.st/2026-06-17-better-slopes-in-aabb-collision-systems/old-slopes-jagged.mp4&#34; type=&#34;video/mp4&#34;&gt;
Your browser does not support the video tag.&lt;/video&gt;&lt;/p&gt;
&lt;figcaption&gt;
  &lt;p&gt;Note: in this clip the ball seems to touch the slope properly, but again, it&#39;s just a visual thing - in reality the slope is a little below the sprite&#39;s visual representation of it.&lt;/p&gt;
&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;Now, when slopes are defined better, we can integrate them into a mock-up platformer to test them in a game environment:&lt;/p&gt;
&lt;p&gt;&lt;video autoplay loop muted&gt;&lt;source src=&#34;https://andreyor.st/2026-06-17-better-slopes-in-aabb-collision-systems/game-slopes.mp4&#34; type=&#34;video/mp4&#34;&gt;
Your browser does not support the video tag.&lt;/video&gt;&lt;/p&gt;
&lt;p&gt;I don&amp;rsquo;t use normal vectors here, since in a platformer the jump usually goes up, regardless of the terrain, but depending on the player&amp;rsquo;s state one could use normals as well, given that we have them in the collision info.&lt;/p&gt;
&lt;p&gt;The old implementation of slopes had problems, that can be seen here:&lt;/p&gt;
&lt;p&gt;&lt;video autoplay loop muted&gt;&lt;source src=&#34;https://andreyor.st/2026-06-17-better-slopes-in-aabb-collision-systems/old-slopes.mp4&#34; type=&#34;video/mp4&#34;&gt;
Your browser does not support the video tag.&lt;/video&gt;&lt;/p&gt;
&lt;p&gt;Notice that when transitioning from slope to solid ground, the player transitions to the falling state for one frame.
This was because we had to handle slope collisions outside of the collider itself, in the update loop - we couldn&amp;rsquo;t cross slopes unless they&amp;rsquo;re a tiny bit higher than the ground next to it.
This problem is now eliminated by handling slopes during bump&amp;rsquo;s collision resolution step.&lt;/p&gt;
&lt;p&gt;But we don&amp;rsquo;t have to stop there - what we have now is basically an extension to normal &lt;abbr title=&#34;Axis-Aligned Bounding-Box&#34;&gt;AABB&lt;/abbr&gt; with custom height functions that are bound by bounding boxes.
So we can create any kind of slopes, like ramps - we just need a proper height function.
However, we did a small optimisation that prevents us from doing it right now - we pre-computed slope normals.
If we use a height function whose normal vector changes along the &lt;code&gt;x&lt;/code&gt; coordinate, we need to handle that in the &lt;code&gt;slope-response&lt;/code&gt; function:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-diff&#34; data-lang=&#34;diff&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt; (fn slope-response [world col x y w h goal-x goal-y filter]
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span                                  &gt;   (let [goal-x (or goal-x x)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span style=&#34;background-color:#efcbcf;&#34;&gt;-        {: surface :slope slope-type : nx : ny} col.other
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span style=&#34;background-color:#bbeabb&#34;&gt;+        {: surface :slope slope-type} col.other
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span                                  &gt;         {:x ox :y oy} col.otherRect
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span style=&#34;background-color:#efcbcf;&#34;&gt;-        {:y touch-y} col.touch]
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span style=&#34;background-color:#bbeabb&#34;&gt;+        cx (- (+ goal-x (/ w 2)) ox)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span style=&#34;background-color:#bbeabb&#34;&gt;+        (nx ny) (slope-normal (- cx 1) (surface (- cx 1))
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span style=&#34;background-color:#bbeabb&#34;&gt;+                              (+ cx 1) (surface (+ cx 1)) slope-type)]
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span                                  &gt;     (var goal-y (or goal-y y))
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span                                  &gt;     (case slope-type
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span                                  &gt;       :floor (let [surf-y (m/min (+ oy (surface (- goal-x ox)))
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span                                  &gt;                                  (+ oy (surface (- (+ goal-x w) ox))))]
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span                                  &gt;                (if (&amp;gt; (+ goal-y h) surf-y)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span style=&#34;background-color:#efcbcf;&#34;&gt;-                   (if (&amp;gt; col.normal.y 0)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span style=&#34;background-color:#efcbcf;&#34;&gt;-                       (set goal-y touch-y)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span style=&#34;background-color:#efcbcf;&#34;&gt;-                       (set [goal-y col.normal.x col.normal.y] [(- surf-y h) nx ny]))
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span style=&#34;background-color:#bbeabb&#34;&gt;+                   (set [goal-y col.normal.x col.normal.y] [(- surf-y h) nx ny])
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span                                  &gt;                    (set [col.normal.x col.normal.y] [0 0])))
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span                                  &gt;       :ceiling (let [surf-y (m/max (+ oy (surface (- goal-x ox)))
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span                                  &gt;                                    (+ oy (surface (- (+ goal-x w) ox))))]
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span                                  &gt;                  (if (&amp;lt; goal-y surf-y)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span style=&#34;background-color:#efcbcf;&#34;&gt;-                     (if (&amp;lt; col.normal.y 0)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span style=&#34;background-color:#efcbcf;&#34;&gt;-                         (set goal-y touch-y)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span style=&#34;background-color:#efcbcf;&#34;&gt;-                         (set [goal-y col.normal.x col.normal.y] [surf-y nx ny]))
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span style=&#34;background-color:#bbeabb&#34;&gt;+                     (set [goal-y col.normal.x col.normal.y] [surf-y nx ny])
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span                                  &gt;                      (set [col.normal.x col.normal.y] [0 0])))
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span                                  &gt;       _ (error (.. &#34;unknown slope type: &#34; (tostring _))))
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span                                  &gt;     (set col.slide {:x goal-x :y goal-y})
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;With that in place, we can even make dynamic slopes, like a moving sine wave, and objects will correctly respond:&lt;/p&gt;
&lt;p&gt;&lt;video autoplay loop muted&gt;&lt;source src=&#34;https://andreyor.st/2026-06-17-better-slopes-in-aabb-collision-systems/sine-slopes.mp4&#34; type=&#34;video/mp4&#34;&gt;
Your browser does not support the video tag.&lt;/video&gt;&lt;/p&gt;
&lt;p&gt;For my purposes, I don&amp;rsquo;t need this kind of flexibility yet, but it&amp;rsquo;s nice to be able to do it still.
I could see this being used in a game like Sonic, where you have loops, which are circles.
Defining four slopes for each circle quadrant (two bottom ones as &lt;code&gt;floor&lt;/code&gt; slopes and upper ones as &lt;code&gt;ceiling&lt;/code&gt; slopes), and handling how to enter and exit them by marking some of them inactive via the &lt;code&gt;cross&lt;/code&gt; collision response.
Handling normals in this case would prove handy, as Sonic jumps perpendicular to the normal at the current position on the slope.&lt;/p&gt;
&lt;p&gt;&lt;video autoplay loop muted&gt;&lt;source src=&#34;https://andreyor.st/2026-06-17-better-slopes-in-aabb-collision-systems/sonic.mp4&#34; type=&#34;video/mp4&#34;&gt;
Your browser does not support the video tag.&lt;/video&gt;&lt;/p&gt;
&lt;p&gt;However, we lost one additional feature our original &lt;code&gt;slope-response&lt;/code&gt; had: these slopes don&amp;rsquo;t have solid opposite side.
Triangular floor slopes prevented objects from entering the slope from beneath, only from the sides and from above the slope.
Sine-wave slope will teleport objects to the slope side if an object entered inside - it&amp;rsquo;s a correctness measure, because of a more complicated geometry.&lt;/p&gt;
&lt;p&gt;That&amp;rsquo;s not a bad thing on its own, since slopes are usually surrounded by other types of ground anyway, but still a thing to consider.&lt;/p&gt;
&lt;p&gt;With that, I have almost perfect slopes for &lt;code&gt;bump.lua&lt;/code&gt;, and I can use it in systems where I can&amp;rsquo;t access more sophisticated collision detection systems like &lt;a href=&#34;https://box2d.org/&#34; target=&#34;_blank&#34;&gt;Box2D&lt;/a&gt;.
There are still things to be aware of, though.
First, because bump&amp;rsquo;s collision resolution does a single pass, we can&amp;rsquo;t handle cases well where a ball touches the slope, another wall, and another ball.
Second, the order of collisions can give different outcomes - if a ball lands exactly in the middle of two slopes, the outcome is determined by which slope &lt;code&gt;bump.lua&lt;/code&gt; will check first.
So it&amp;rsquo;s good for simple cases, but not a proper slope collision simulation, like one found in non-AABB systems.&lt;/p&gt;
&lt;p&gt;On that note, I think I&amp;rsquo;m satisfied with what I have, and I&amp;rsquo;ll try to use this in my next game projects!
And, as always, the &lt;a href=&#34;https://tic80.com/play?cart=4730&#34; target=&#34;_blank&#34;&gt;you can try this online here&lt;/a&gt;, and a downloadable version of a cart is provided below:&lt;/p&gt;
&lt;figure&gt;&lt;img src=&#34;https://andreyor.st/2026-06-17-better-slopes-in-aabb-collision-systems/aabb-slopes.png&#34;/&gt;
&lt;/figure&gt;

&lt;div class=&#34;comment-link&#34;&gt; &lt;a href=&#34;mailto:%61%6e%64%72%65%79%6f%72%73%74%2b%62%6c%6f%67%40%67%6d%61%69%6c%2e%63%6f%6d?subject=Comment: Better slopes in AABB collision systems&#34; target=&#34;_blank&#34; rel=&#34;noopener noreferrer&#34;&gt;Comment via email&lt;/a&gt;&lt;/div&gt;</description>
      <pubDate>Wed, 17 Jun 2026 19:14:00 +0300</pubDate>
    </item><item>
      <title>Spring Lisp Game Jam 2026</title>
      <link>https://andreyor.st/posts/2026-05-24-spring-lisp-game-jam-2026/</link>
      <guid>https://andreyor.st/posts/2026-05-24-spring-lisp-game-jam-2026/</guid>
      <description>&lt;p&gt;This year I decided to participate in the &lt;a href=&#34;https://itch.io/jam/spring-lisp-game-jam-2026&#34; target=&#34;_blank&#34;&gt;Spring Lisp Game Jam&lt;/a&gt;.
It&amp;rsquo;s an annual event, where you have to make a game in any kind of lisp in a limited time, usually a week.&lt;/p&gt;
&lt;p&gt;I&amp;rsquo;ve been putting this away for several years, because every time the jam started I wasn&amp;rsquo;t ready to spend time on it, because of work or other duties I had at the time.
This year, however, I decided to take a vacation, and give it my all.
Life, of course, had other plans, but I managed to persevere.&lt;/p&gt;
&lt;p&gt;This game jam doesn&amp;rsquo;t have a particular theme, which is good, as I always wanted to make a particular kind of a game - a movement-oriented platformer.
I really enjoyed games like Metroid Fusion and Metroid Dread, which, to my taste, have great platforming, especially in Dread, so I wanted to make something akin to it.
On paper, this idea seemed doable.
In practice, well, you&amp;rsquo;ll see.&lt;/p&gt;
&lt;p&gt;I decided to make &amp;ldquo;speed&amp;rdquo; the main thing of my game, thinking that the core idea will be to maintain speed, and complete room-based obstacle courses.
My engine of choice was again &lt;a href=&#34;https://tic80.com/&#34; target=&#34;_blank&#34;&gt;TIC-80&lt;/a&gt; as I had some experience with it already, and it&amp;rsquo;s the most frictionless environment I know for making small games.
That&amp;rsquo;s debatable, of course, given TIC&amp;rsquo;s restrictions of resolution of 240x136 pixels and a 16-color-only palette.
But I like restrictions, they breed creativity, as we all know.&lt;/p&gt;
&lt;p&gt;This is more of a postmortem post, as I&amp;rsquo;m writing this after finishing the game - I simply had no capacity of writing this during the development.
So information below is more of how I remember this, and of notes I made along the way.&lt;/p&gt;
&lt;h2 id=&#34;day-1&#34;&gt;Day 1&lt;/h2&gt;
&lt;p&gt;First things first, I chose TIC-80, and it supports programming in lisp out of the box.
The language in question is Fennel, which is a nice lisp that compiles to Lua.
I needed a few things though.&lt;/p&gt;
&lt;p&gt;TIC doesn&amp;rsquo;t have any built-in functionality besides the essential things for drawing on the screen and reading user&amp;rsquo;s input.
My game needs to deal with collisions, and while other game engines, like LOVE, include a library for that, TIC is certainly not.&lt;/p&gt;
&lt;p&gt;A popular library for this is &lt;a href=&#34;https://github.com/kikito/bump.lua&#34; target=&#34;_blank&#34;&gt;bump.lua&lt;/a&gt;, however, TIC doesn&amp;rsquo;t allow filesystem interactions, so the library must be bundled inside the cart.
However, this is a Lua library, and I&amp;rsquo;m making a game in Fennel.&lt;/p&gt;
&lt;p&gt;Fennel has a way of inlining dependencies via the &lt;code&gt;--require-as-include&lt;/code&gt; option that could be used, however, it only works when compiling Fennel code to Lua.
And I&amp;rsquo;m not sure if this will pass for the game jam.
Plus, Fennel doesn&amp;rsquo;t compile comments, so I couldn&amp;rsquo;t just write the game cart in Fennel and then compile it to Lua, I&amp;rsquo;d need to translate all of the cart metadata by hand.&lt;/p&gt;
&lt;p&gt;Luckily, a few years ago I made a port of bump.lua to Fennel for cases like this.
I used it in the &lt;a href=&#34;https://gitlab.com/andreyorst/game1/-/blob/main/cart.fnl&#34; target=&#34;_blank&#34;&gt;other game&lt;/a&gt; I was making at the time.
You can read my old dev-log &lt;a href=&#34;https://andreyor.st/tags/game1&#34;&gt;here&lt;/a&gt;.
Anyhow, I needed to make an actual game, not just mess with libraries, so I &lt;a href=&#34;https://lospec.com/palette-list/urbex-16&#34; target=&#34;_blank&#34;&gt;picked a palette&lt;/a&gt; and began working.&lt;/p&gt;
&lt;p&gt;First day was all about player controller.
I needed a solid foundation to build the game around, and none of my previous player controllers were adequate for this.
They were too stiff.&lt;/p&gt;
&lt;p&gt;At the end of the day I ended up with this:&lt;/p&gt;
&lt;p&gt;&lt;video autoplay loop muted&gt;&lt;source src=&#34;https://andreyor.st/2026-05-24-spring-lisp-game-jam-2026/day-1.mp4&#34; type=&#34;video/mp4&#34;&gt;
Your browser does not support the video tag.&lt;/video&gt;&lt;/p&gt;
&lt;p&gt;This is a bit more than just a player controller - I have a camera system, level editor, placeholder graphics, and powerups already coded.&lt;/p&gt;
&lt;p&gt;This is my camera system:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-fennel&#34; data-lang=&#34;fennel&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;(&lt;span style=&#34;font-weight:bold;font-style:italic&#34;&gt;local &lt;/span&gt;&lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;camera&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  {&lt;span style=&#34;color:#666;font-style:italic&#34;&gt;:x&lt;/span&gt; 96
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;   &lt;span style=&#34;color:#666;font-style:italic&#34;&gt;:y&lt;/span&gt; 40
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;   &lt;span style=&#34;color:#666;font-style:italic&#34;&gt;:update&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;   (&lt;span style=&#34;font-weight:bold;font-style:italic&#34;&gt;fn &lt;/span&gt;[&lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;self&lt;/span&gt; {&lt;span style=&#34;font-weight:bold&#34;&gt;: &lt;/span&gt;&lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;x&lt;/span&gt; &lt;span style=&#34;font-weight:bold&#34;&gt;: &lt;/span&gt;&lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;y&lt;/span&gt;}]
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;     (&lt;span style=&#34;font-weight:bold&#34;&gt;doto &lt;/span&gt;&lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;self&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;       (&lt;span style=&#34;font-weight:bold&#34;&gt;tset &lt;/span&gt;&lt;span style=&#34;color:#666;font-style:italic&#34;&gt;:x&lt;/span&gt; (&lt;span style=&#34;font-weight:bold&#34;&gt;* &lt;/span&gt;(&lt;span style=&#34;font-weight:bold&#34;&gt;// &lt;/span&gt;&lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;x&lt;/span&gt; 240) 240))
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;       (&lt;span style=&#34;font-weight:bold&#34;&gt;tset &lt;/span&gt;&lt;span style=&#34;color:#666;font-style:italic&#34;&gt;:y&lt;/span&gt; (&lt;span style=&#34;font-weight:bold&#34;&gt;* &lt;/span&gt;(&lt;span style=&#34;font-weight:bold&#34;&gt;// &lt;/span&gt;&lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;y&lt;/span&gt; 136) 136))))
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;   &lt;span style=&#34;color:#666;font-style:italic&#34;&gt;:draw&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;   (&lt;span style=&#34;font-weight:bold;font-style:italic&#34;&gt;fn &lt;/span&gt;[{&lt;span style=&#34;font-weight:bold&#34;&gt;: &lt;/span&gt;&lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;x&lt;/span&gt; &lt;span style=&#34;font-weight:bold&#34;&gt;: &lt;/span&gt;&lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;y&lt;/span&gt;} &lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;remap&lt;/span&gt;]
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;     (&lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;map&lt;/span&gt; (&lt;span style=&#34;font-weight:bold&#34;&gt;// &lt;/span&gt;&lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;x&lt;/span&gt; 8) (&lt;span style=&#34;font-weight:bold&#34;&gt;// &lt;/span&gt;&lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;y&lt;/span&gt; 8) (&lt;span style=&#34;font-weight:bold&#34;&gt;+ &lt;/span&gt;(&lt;span style=&#34;font-weight:bold&#34;&gt;// &lt;/span&gt;&lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;x&lt;/span&gt; 8) 31) (&lt;span style=&#34;font-weight:bold&#34;&gt;+ &lt;/span&gt;(&lt;span style=&#34;font-weight:bold&#34;&gt;// &lt;/span&gt;&lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;y&lt;/span&gt; 8) 18) 0 0 0 1 &lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;remap&lt;/span&gt;))
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;   &lt;span style=&#34;color:#666;font-style:italic&#34;&gt;:translate&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;   (&lt;span style=&#34;font-weight:bold;font-style:italic&#34;&gt;fn &lt;/span&gt;[{&lt;span style=&#34;color:#666;font-style:italic&#34;&gt;:x&lt;/span&gt; &lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;cam-x&lt;/span&gt; &lt;span style=&#34;color:#666;font-style:italic&#34;&gt;:y&lt;/span&gt; &lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;cam-y&lt;/span&gt;} &lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;x&lt;/span&gt; &lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;y&lt;/span&gt;]
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;     (&lt;span style=&#34;font-weight:bold&#34;&gt;values &lt;/span&gt;(&lt;span style=&#34;font-weight:bold&#34;&gt;- &lt;/span&gt;&lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;x&lt;/span&gt; &lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;cam-x&lt;/span&gt;) (&lt;span style=&#34;font-weight:bold&#34;&gt;- &lt;/span&gt;&lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;y&lt;/span&gt; &lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;cam-y&lt;/span&gt;)))})
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;It&amp;rsquo;s a simple object that has three main methods.&lt;/p&gt;
&lt;p&gt;The &lt;code&gt;update&lt;/code&gt; method is responsible for camera positioning.
It is called on each frame, and it simply clamps the &lt;code&gt;x, y&lt;/code&gt; position into the one full screen range.
Thus, this is not a free camera, it is locked to the current screen.&lt;/p&gt;
&lt;p&gt;I had dynamic cameras in the past, and while I like them, for this game I wanted something different.
Essentially, my vision was that each screen will be an obstacle course, although even at this point I already knew this would be a bit problematic.&lt;/p&gt;
&lt;p&gt;The &lt;code&gt;draw&lt;/code&gt; method simply draws the current visible range of tiles via the &lt;code&gt;map&lt;/code&gt; command.
I have a &lt;code&gt;remap&lt;/code&gt; callback ready in case I&amp;rsquo;ll need to change some tiles dynamically.&lt;/p&gt;
&lt;p&gt;The final method &lt;code&gt;translate&lt;/code&gt; is an interesting one.
When I draw anything, like the player, or interactable objects, I need to do so in the camera space.
So I call &lt;code&gt;(camera:translate obj.x obj.y)&lt;/code&gt; to get screen-space coordinates.
In other words, object&amp;rsquo;s coordinates may be &lt;code&gt;1337,420&lt;/code&gt;, but in camera space they as well may be &lt;code&gt;42,27&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;The camera draws the level from the map editor built into TIC, but we need to load it into the game to interact with it properly.
So we need a system to do that.&lt;/p&gt;
&lt;p&gt;The level editor is another thing I reused from the last game I made with TIC.&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-fennel&#34; data-lang=&#34;fennel&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;(&lt;span style=&#34;font-weight:bold;font-style:italic&#34;&gt;local &lt;/span&gt;&lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;objects&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  {1 &lt;span style=&#34;color:#666;font-style:italic&#34;&gt;:player&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;   17 &lt;span style=&#34;color:#666;font-style:italic&#34;&gt;:speed-up&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;   33 &lt;span style=&#34;color:#666;font-style:italic&#34;&gt;:jump-up&lt;/span&gt;})
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;(&lt;span style=&#34;font-weight:bold;font-style:italic&#34;&gt;macro &lt;/span&gt;&lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;id&lt;/span&gt; [&lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;x&lt;/span&gt; &lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;y&lt;/span&gt;]
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  `(&lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;mget&lt;/span&gt; (&lt;span style=&#34;font-weight:bold&#34;&gt;// &lt;/span&gt;&lt;span style=&#34;&#34;&gt;,&lt;/span&gt;&lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;x&lt;/span&gt; 8) (&lt;span style=&#34;font-weight:bold&#34;&gt;// &lt;/span&gt;&lt;span style=&#34;&#34;&gt;,&lt;/span&gt;&lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;y&lt;/span&gt; 8)))
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;(&lt;span style=&#34;font-weight:bold;font-style:italic&#34;&gt;fn &lt;/span&gt;&lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;obj?&lt;/span&gt; [&lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;x&lt;/span&gt; &lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;y&lt;/span&gt;]
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  (&lt;span style=&#34;font-weight:bold&#34;&gt;. &lt;/span&gt;&lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;objects&lt;/span&gt; (&lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;id&lt;/span&gt; &lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;x&lt;/span&gt; &lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;y&lt;/span&gt;)))
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;(&lt;span style=&#34;font-weight:bold;font-style:italic&#34;&gt;fn &lt;/span&gt;&lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;solid?&lt;/span&gt; [&lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;x&lt;/span&gt; &lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;y&lt;/span&gt;]
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  (&lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;fget&lt;/span&gt; (&lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;id&lt;/span&gt; &lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;x&lt;/span&gt; &lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;y&lt;/span&gt;) 0))
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;(&lt;span style=&#34;font-weight:bold;font-style:italic&#34;&gt;fn &lt;/span&gt;&lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;load-map-to-world&lt;/span&gt; []
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  (&lt;span style=&#34;font-weight:bold&#34;&gt;for &lt;/span&gt;[&lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;x&lt;/span&gt; 0 (&lt;span style=&#34;font-weight:bold&#34;&gt;* &lt;/span&gt;240 8) 8]
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    (&lt;span style=&#34;font-weight:bold&#34;&gt;for &lt;/span&gt;[&lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;y&lt;/span&gt; 0 (&lt;span style=&#34;font-weight:bold&#34;&gt;* &lt;/span&gt;136 8) 8]
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      (&lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;case&lt;/span&gt; (&lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;obj?&lt;/span&gt; &lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;x&lt;/span&gt; &lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;y&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#666;font-style:italic&#34;&gt;:player&lt;/span&gt; (&lt;span style=&#34;font-weight:bold&#34;&gt;do &lt;/span&gt;(&lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;mset&lt;/span&gt; (&lt;span style=&#34;font-weight:bold&#34;&gt;// &lt;/span&gt;&lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;x&lt;/span&gt; 8) (&lt;span style=&#34;font-weight:bold&#34;&gt;// &lt;/span&gt;&lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;y&lt;/span&gt; 8) 0)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                    (&lt;span style=&#34;font-weight:bold&#34;&gt;doto &lt;/span&gt;&lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;player&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                      (&lt;span style=&#34;font-weight:bold&#34;&gt;tset &lt;/span&gt;&lt;span style=&#34;color:#666;font-style:italic&#34;&gt;:x&lt;/span&gt; &lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;x&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                      (&lt;span style=&#34;font-weight:bold&#34;&gt;tset &lt;/span&gt;&lt;span style=&#34;color:#666;font-style:italic&#34;&gt;:y&lt;/span&gt; &lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;y&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                      (&lt;span style=&#34;font-weight:bold&#34;&gt;tset &lt;/span&gt;&lt;span style=&#34;color:#666;font-style:italic&#34;&gt;:spawn-x&lt;/span&gt; &lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;x&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                      (&lt;span style=&#34;font-weight:bold&#34;&gt;tset &lt;/span&gt;&lt;span style=&#34;color:#666;font-style:italic&#34;&gt;:spawn-y&lt;/span&gt; &lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;y&lt;/span&gt;))
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                    (&lt;span style=&#34;font-weight:bold&#34;&gt;doto &lt;/span&gt;&lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;camera&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                      (&lt;span style=&#34;font-weight:bold&#34;&gt;tset &lt;/span&gt;&lt;span style=&#34;color:#666;font-style:italic&#34;&gt;:x&lt;/span&gt; (&lt;span style=&#34;font-weight:bold&#34;&gt;* &lt;/span&gt;&lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;x&lt;/span&gt; 8))
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                      (&lt;span style=&#34;font-weight:bold&#34;&gt;tset &lt;/span&gt;&lt;span style=&#34;color:#666;font-style:italic&#34;&gt;:y&lt;/span&gt; (&lt;span style=&#34;font-weight:bold&#34;&gt;* &lt;/span&gt;&lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;y&lt;/span&gt; 8)))
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                    (&lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;world&lt;/span&gt;&lt;span style=&#34;color:#666;font-style:italic&#34;&gt;:add&lt;/span&gt; &lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;player&lt;/span&gt; &lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;player.x&lt;/span&gt; &lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;player.y&lt;/span&gt; &lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;player.w&lt;/span&gt; &lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;player.h&lt;/span&gt;))
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#666;font-style:italic&#34;&gt;:speed-up&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        (&lt;span style=&#34;font-weight:bold&#34;&gt;let &lt;/span&gt;[&lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;obj&lt;/span&gt; {&lt;span style=&#34;font-weight:bold&#34;&gt;: &lt;/span&gt;&lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;x&lt;/span&gt; &lt;span style=&#34;font-weight:bold&#34;&gt;: &lt;/span&gt;&lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;y&lt;/span&gt; &lt;span style=&#34;color:#666;font-style:italic&#34;&gt;:h&lt;/span&gt; 8 &lt;span style=&#34;color:#666;font-style:italic&#34;&gt;:w&lt;/span&gt; 8 &lt;span style=&#34;color:#666;font-style:italic&#34;&gt;:type&lt;/span&gt; &lt;span style=&#34;color:#666;font-style:italic&#34;&gt;:cross&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                   &lt;span style=&#34;color:#666;font-style:italic&#34;&gt;:handle&lt;/span&gt; (&lt;span style=&#34;font-weight:bold;font-style:italic&#34;&gt;fn &lt;/span&gt;[&lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;self&lt;/span&gt; &lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;target&lt;/span&gt;]
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                             (&lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;world&lt;/span&gt;&lt;span style=&#34;color:#666;font-style:italic&#34;&gt;:remove&lt;/span&gt; &lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;self&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                             (&lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;mset&lt;/span&gt; (&lt;span style=&#34;font-weight:bold&#34;&gt;// &lt;/span&gt;&lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;self.x&lt;/span&gt; 8) (&lt;span style=&#34;font-weight:bold&#34;&gt;// &lt;/span&gt;&lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;self.y&lt;/span&gt; 8) 0)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                             (&lt;span style=&#34;font-weight:bold&#34;&gt;set &lt;/span&gt;&lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;target.max-xvel&lt;/span&gt; (&lt;span style=&#34;font-weight:bold&#34;&gt;+ &lt;/span&gt;&lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;target.max-xvel&lt;/span&gt; 1)))}]
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;          (&lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;world&lt;/span&gt;&lt;span style=&#34;color:#666;font-style:italic&#34;&gt;:add&lt;/span&gt; &lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;obj&lt;/span&gt; &lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;obj.x&lt;/span&gt; &lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;obj.y&lt;/span&gt; &lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;obj.w&lt;/span&gt; &lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;obj.h&lt;/span&gt;))
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#666;font-style:italic&#34;&gt;:jump-up&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        (&lt;span style=&#34;font-weight:bold&#34;&gt;let &lt;/span&gt;[&lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;obj&lt;/span&gt; {&lt;span style=&#34;font-weight:bold&#34;&gt;: &lt;/span&gt;&lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;x&lt;/span&gt; &lt;span style=&#34;font-weight:bold&#34;&gt;: &lt;/span&gt;&lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;y&lt;/span&gt; &lt;span style=&#34;color:#666;font-style:italic&#34;&gt;:h&lt;/span&gt; 8 &lt;span style=&#34;color:#666;font-style:italic&#34;&gt;:w&lt;/span&gt; 8 &lt;span style=&#34;color:#666;font-style:italic&#34;&gt;:type&lt;/span&gt; &lt;span style=&#34;color:#666;font-style:italic&#34;&gt;:cross&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                   &lt;span style=&#34;color:#666;font-style:italic&#34;&gt;:handle&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                   (&lt;span style=&#34;font-weight:bold;font-style:italic&#34;&gt;fn &lt;/span&gt;[&lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;self&lt;/span&gt; &lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;target&lt;/span&gt;]
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                     (&lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;world&lt;/span&gt;&lt;span style=&#34;color:#666;font-style:italic&#34;&gt;:remove&lt;/span&gt; &lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;self&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                     (&lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;mset&lt;/span&gt; (&lt;span style=&#34;font-weight:bold&#34;&gt;// &lt;/span&gt;&lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;self.x&lt;/span&gt; 8) (&lt;span style=&#34;font-weight:bold&#34;&gt;// &lt;/span&gt;&lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;self.y&lt;/span&gt; 8) 0)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                     (&lt;span style=&#34;font-weight:bold&#34;&gt;set &lt;/span&gt;&lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;target.jump-vel&lt;/span&gt; (&lt;span style=&#34;font-weight:bold&#34;&gt;- &lt;/span&gt;&lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;target.jump-vel&lt;/span&gt; 2)))}]
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;          (&lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;world&lt;/span&gt;&lt;span style=&#34;color:#666;font-style:italic&#34;&gt;:add&lt;/span&gt; &lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;obj&lt;/span&gt; &lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;obj.x&lt;/span&gt; &lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;obj.y&lt;/span&gt; &lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;obj.w&lt;/span&gt; &lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;obj.h&lt;/span&gt;))
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;_&lt;/span&gt; (&lt;span style=&#34;font-weight:bold&#34;&gt;if &lt;/span&gt;(&lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;solid?&lt;/span&gt; &lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;x&lt;/span&gt; &lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;y&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;              (&lt;span style=&#34;font-weight:bold&#34;&gt;let &lt;/span&gt;[&lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;obj&lt;/span&gt; {&lt;span style=&#34;font-weight:bold&#34;&gt;: &lt;/span&gt;&lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;x&lt;/span&gt; &lt;span style=&#34;font-weight:bold&#34;&gt;: &lt;/span&gt;&lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;y&lt;/span&gt; &lt;span style=&#34;color:#666;font-style:italic&#34;&gt;:h&lt;/span&gt; 8 &lt;span style=&#34;color:#666;font-style:italic&#34;&gt;:w&lt;/span&gt; 8}]
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                (&lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;world&lt;/span&gt;&lt;span style=&#34;color:#666;font-style:italic&#34;&gt;:add&lt;/span&gt; &lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;obj&lt;/span&gt; &lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;obj.x&lt;/span&gt; &lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;obj.y&lt;/span&gt; &lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;obj.w&lt;/span&gt; &lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;obj.h&lt;/span&gt;)))))))
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;It&amp;rsquo;s a simple loop that goes over each map tile, and decides if it is a thing that we need to register in bump&amp;rsquo;s &lt;code&gt;world&lt;/code&gt;.
To decide that I use a combination of tile ids and flags.
It&amp;rsquo;s called at the start of the game, and loads the map once.&lt;/p&gt;
&lt;p&gt;Finally, the most convoluted part was the player controller:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-fennel&#34; data-lang=&#34;fennel&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;(&lt;span style=&#34;font-weight:bold;font-style:italic&#34;&gt;fn &lt;/span&gt;&lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;player.update&lt;/span&gt; [&lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;self&lt;/span&gt; &lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;_transition-target&lt;/span&gt;]
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  (&lt;span style=&#34;font-weight:bold&#34;&gt;let &lt;/span&gt;[&lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;grounded?&lt;/span&gt; (&lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;grounded?&lt;/span&gt; &lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;self&lt;/span&gt; &lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;collision-filter&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;wall-bound?&lt;/span&gt; (&lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;wall-bound?&lt;/span&gt; &lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;self&lt;/span&gt; &lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;collision-filter&lt;/span&gt;)]
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    (&lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;echoes&lt;/span&gt;&lt;span style=&#34;color:#666;font-style:italic&#34;&gt;:update&lt;/span&gt; &lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;self&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    (&lt;span style=&#34;font-weight:bold&#34;&gt;when &lt;/span&gt;(&lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;bonk?&lt;/span&gt; &lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;self&lt;/span&gt; &lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;collision-filter&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      (&lt;span style=&#34;font-weight:bold&#34;&gt;set &lt;/span&gt;&lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;self.yvel&lt;/span&gt; 0))
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    (&lt;span style=&#34;font-weight:bold&#34;&gt;if &lt;/span&gt;&lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;grounded?&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        (&lt;span style=&#34;font-weight:bold&#34;&gt;set &lt;/span&gt;&lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;self.yvel&lt;/span&gt; 0)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        (&lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;apply-gravity&lt;/span&gt; &lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;self&lt;/span&gt; &lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;wall-bound?&lt;/span&gt;))
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    (&lt;span style=&#34;font-weight:bold&#34;&gt;if &lt;/span&gt;(&lt;span style=&#34;font-weight:bold&#34;&gt;and &lt;/span&gt;(&lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;btn&lt;/span&gt; &lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;right&lt;/span&gt;) (&lt;span style=&#34;font-weight:bold&#34;&gt;or &lt;/span&gt;&lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;grounded?&lt;/span&gt; (&lt;span style=&#34;font-weight:bold&#34;&gt;not &lt;/span&gt;&lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;wall-bound?&lt;/span&gt;)))
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        (&lt;span style=&#34;font-weight:bold&#34;&gt;doto &lt;/span&gt;&lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;self&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;          (&lt;span style=&#34;font-weight:bold&#34;&gt;tset &lt;/span&gt;&lt;span style=&#34;color:#666;font-style:italic&#34;&gt;:flip&lt;/span&gt; 1)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;          (&lt;span style=&#34;font-weight:bold&#34;&gt;tset &lt;/span&gt;&lt;span style=&#34;color:#666;font-style:italic&#34;&gt;:xvel&lt;/span&gt; (&lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;lerp&lt;/span&gt; &lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;self.xvel&lt;/span&gt; (&lt;span style=&#34;font-weight:bold&#34;&gt;if &lt;/span&gt;&lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;wall-bound?&lt;/span&gt; (&lt;span style=&#34;font-weight:bold&#34;&gt;/ &lt;/span&gt;&lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;self.max-xvel&lt;/span&gt; 5) &lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;self.max-xvel&lt;/span&gt;) (&lt;span style=&#34;font-weight:bold&#34;&gt;if &lt;/span&gt;&lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;grounded?&lt;/span&gt; &lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;acc&lt;/span&gt; 0.01))))
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        (&lt;span style=&#34;font-weight:bold&#34;&gt;and &lt;/span&gt;(&lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;btn&lt;/span&gt; &lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;left&lt;/span&gt;) (&lt;span style=&#34;font-weight:bold&#34;&gt;or &lt;/span&gt;&lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;grounded?&lt;/span&gt; (&lt;span style=&#34;font-weight:bold&#34;&gt;not &lt;/span&gt;&lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;wall-bound?&lt;/span&gt;)))
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        (&lt;span style=&#34;font-weight:bold&#34;&gt;doto &lt;/span&gt;&lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;self&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;          (&lt;span style=&#34;font-weight:bold&#34;&gt;tset &lt;/span&gt;&lt;span style=&#34;color:#666;font-style:italic&#34;&gt;:flip&lt;/span&gt; -1)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;          (&lt;span style=&#34;font-weight:bold&#34;&gt;tset &lt;/span&gt;&lt;span style=&#34;color:#666;font-style:italic&#34;&gt;:xvel&lt;/span&gt; (&lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;lerp&lt;/span&gt; &lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;self.xvel&lt;/span&gt; (&lt;span style=&#34;font-weight:bold&#34;&gt;if &lt;/span&gt;&lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;wall-bound?&lt;/span&gt; (&lt;span style=&#34;font-weight:bold&#34;&gt;/ &lt;/span&gt;&lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;self.max-xvel&lt;/span&gt; -5) (&lt;span style=&#34;font-weight:bold&#34;&gt;- &lt;/span&gt;&lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;self.max-xvel&lt;/span&gt;)) (&lt;span style=&#34;font-weight:bold&#34;&gt;if &lt;/span&gt;&lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;grounded?&lt;/span&gt; &lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;acc&lt;/span&gt; 0.01))))
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;grounded?&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        (&lt;span style=&#34;font-weight:bold&#34;&gt;if &lt;/span&gt;&lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;wall-bound?&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            (&lt;span style=&#34;font-weight:bold&#34;&gt;set &lt;/span&gt;&lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;self.xvel&lt;/span&gt; 0)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            (&lt;span style=&#34;font-weight:bold&#34;&gt;let &lt;/span&gt;[]
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;              (&lt;span style=&#34;font-weight:bold&#34;&gt;set &lt;/span&gt;&lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;self.xvel&lt;/span&gt; (&lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;snap-to-zero&lt;/span&gt; (&lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;round&lt;/span&gt; (&lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;lerp&lt;/span&gt; &lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;self.xvel&lt;/span&gt; 0 0.1)))))))
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    (&lt;span style=&#34;font-weight:bold&#34;&gt;when &lt;/span&gt;(&lt;span style=&#34;font-weight:bold&#34;&gt;or &lt;/span&gt;(&lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;btnp&lt;/span&gt; &lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;A&lt;/span&gt;) (&lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;btnp&lt;/span&gt; &lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;B&lt;/span&gt;) (&lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;btnp&lt;/span&gt; &lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;X&lt;/span&gt;) (&lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;btnp&lt;/span&gt; &lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;Y&lt;/span&gt;))
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      (&lt;span style=&#34;font-weight:bold&#34;&gt;if &lt;/span&gt;&lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;grounded?&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;          (&lt;span style=&#34;font-weight:bold&#34;&gt;set &lt;/span&gt;&lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;self.yvel&lt;/span&gt; &lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;self.jump-vel&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;          &lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;wall-bound?&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;          (&lt;span style=&#34;font-weight:bold&#34;&gt;doto &lt;/span&gt;&lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;self&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            (&lt;span style=&#34;font-weight:bold&#34;&gt;tset &lt;/span&gt;&lt;span style=&#34;color:#666;font-style:italic&#34;&gt;:flip&lt;/span&gt; (&lt;span style=&#34;font-weight:bold&#34;&gt;- &lt;/span&gt;&lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;self.flip&lt;/span&gt;))
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            (&lt;span style=&#34;font-weight:bold&#34;&gt;tset &lt;/span&gt;&lt;span style=&#34;color:#666;font-style:italic&#34;&gt;:xvel&lt;/span&gt; (&lt;span style=&#34;font-weight:bold&#34;&gt;if &lt;/span&gt;(&lt;span style=&#34;font-weight:bold&#34;&gt;= &lt;/span&gt;&lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;self.xvel&lt;/span&gt; 0)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                            (&lt;span style=&#34;font-weight:bold&#34;&gt;* &lt;/span&gt;3 (&lt;span style=&#34;font-weight:bold&#34;&gt;- &lt;/span&gt;&lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;wall-bound?&lt;/span&gt;))
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                            (&lt;span style=&#34;font-weight:bold&#34;&gt;- &lt;/span&gt;&lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;self.xvel&lt;/span&gt;)))
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            (&lt;span style=&#34;font-weight:bold&#34;&gt;tset &lt;/span&gt;&lt;span style=&#34;color:#666;font-style:italic&#34;&gt;:yvel&lt;/span&gt; (&lt;span style=&#34;font-weight:bold&#34;&gt;* &lt;/span&gt;&lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;self.jump-vel&lt;/span&gt; 0.75)))))
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    (&lt;span style=&#34;font-weight:bold&#34;&gt;let &lt;/span&gt;[(&lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;x&lt;/span&gt; &lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;y&lt;/span&gt; &lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;cols&lt;/span&gt; &lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;len&lt;/span&gt;) (&lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;world&lt;/span&gt;&lt;span style=&#34;color:#666;font-style:italic&#34;&gt;:move&lt;/span&gt; &lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;self&lt;/span&gt; (&lt;span style=&#34;font-weight:bold&#34;&gt;+ &lt;/span&gt;&lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;self.x&lt;/span&gt; &lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;self.xvel&lt;/span&gt;) (&lt;span style=&#34;font-weight:bold&#34;&gt;+ &lt;/span&gt;&lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;self.y&lt;/span&gt; &lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;self.yvel&lt;/span&gt;) &lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;collision-filter&lt;/span&gt;)]
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      (&lt;span style=&#34;font-weight:bold&#34;&gt;for &lt;/span&gt;[&lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;i&lt;/span&gt; 1 &lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;len&lt;/span&gt;]
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        (&lt;span style=&#34;font-weight:bold&#34;&gt;let &lt;/span&gt;[{&lt;span style=&#34;font-weight:bold&#34;&gt;: &lt;/span&gt;&lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;other&lt;/span&gt;} (&lt;span style=&#34;font-weight:bold&#34;&gt;. &lt;/span&gt;&lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;cols&lt;/span&gt; &lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;i&lt;/span&gt;)]
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;          (&lt;span style=&#34;font-weight:bold&#34;&gt;if &lt;/span&gt;&lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;other.handle&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;              (&lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;other&lt;/span&gt;&lt;span style=&#34;color:#666;font-style:italic&#34;&gt;:handle&lt;/span&gt; &lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;self&lt;/span&gt;))))
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      (&lt;span style=&#34;font-weight:bold&#34;&gt;set &lt;/span&gt;&lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;self.x&lt;/span&gt; &lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;x&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      (&lt;span style=&#34;font-weight:bold&#34;&gt;set &lt;/span&gt;&lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;self.y&lt;/span&gt; &lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;y&lt;/span&gt;))))
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;This is again a method in the &lt;code&gt;player&lt;/code&gt; object, which holds some state, like coordinates and other attributes of the player, like max speed.
It&amp;rsquo;s fairly simple right now, but it&amp;rsquo;ll get worse in the future days.&lt;/p&gt;
&lt;p&gt;One of the things you may have noticed was a trail effect, when the player reaches a certain speed.
I wanted it in the game, inspired by Super Metroid&amp;rsquo;s speed booster effect.
However, my small brain couldn&amp;rsquo;t figure out how to do it properly, so I simply used linear interpolation so each echo lags behind the player object just enough, and used time offsets to delay their animations.
Later, I realized that I can do it simply by storing last player position every few frames and drawing the echo this way, but I had no time to fix it.&lt;/p&gt;
&lt;p&gt;The animation system is another thing I needed to make myself, as TIC doesn&amp;rsquo;t have anything like that.
There&amp;rsquo;s a popular library for doing that, called &lt;a href=&#34;https://github.com/kikito/anim8&#34; target=&#34;_blank&#34;&gt;anim8&lt;/a&gt;, by the same author of bump.lua.
I didn&amp;rsquo;t use it, as I had to learn it, and wasn&amp;rsquo;t sure if it&amp;rsquo;s compatible with TIC at all.&lt;/p&gt;
&lt;p&gt;So my animation system is as follows:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-fennel&#34; data-lang=&#34;fennel&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;(&lt;span style=&#34;font-weight:bold;font-style:italic&#34;&gt;fn &lt;/span&gt;&lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;draw-anim&lt;/span&gt; [&lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;self&lt;/span&gt; &lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;x&lt;/span&gt; &lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;y&lt;/span&gt; &lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;flip?&lt;/span&gt; &lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;speed&lt;/span&gt;]
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  (&lt;span style=&#34;font-weight:bold&#34;&gt;when &lt;/span&gt;(&lt;span style=&#34;font-weight:bold&#34;&gt;= &lt;/span&gt;(&lt;span style=&#34;font-weight:bold&#34;&gt;% &lt;/span&gt;&lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;t&lt;/span&gt; &lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;speed&lt;/span&gt;) 0)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    (&lt;span style=&#34;font-weight:bold&#34;&gt;set &lt;/span&gt;&lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;self.frame&lt;/span&gt; (&lt;span style=&#34;font-weight:bold&#34;&gt;% &lt;/span&gt;(&lt;span style=&#34;font-weight:bold&#34;&gt;+ &lt;/span&gt;&lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;self.frame&lt;/span&gt; 1) &lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;self.frames&lt;/span&gt;)))
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  (&lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;spr&lt;/span&gt; (&lt;span style=&#34;font-weight:bold&#34;&gt;+ &lt;/span&gt;&lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;self.start&lt;/span&gt; (&lt;span style=&#34;font-weight:bold&#34;&gt;* &lt;/span&gt;&lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;self.frame&lt;/span&gt; 2)) (&lt;span style=&#34;font-weight:bold&#34;&gt;- &lt;/span&gt;&lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;x&lt;/span&gt; 4) &lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;y&lt;/span&gt; 0 1 &lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;flip?&lt;/span&gt; 0 2 2))
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;(&lt;span style=&#34;font-weight:bold;font-style:italic&#34;&gt;local &lt;/span&gt;&lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;run-anim&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  {&lt;span style=&#34;color:#666;font-style:italic&#34;&gt;:start&lt;/span&gt; 258
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;   &lt;span style=&#34;color:#666;font-style:italic&#34;&gt;:end&lt;/span&gt; 268
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;   &lt;span style=&#34;color:#666;font-style:italic&#34;&gt;:frames&lt;/span&gt; 6
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;   &lt;span style=&#34;color:#666;font-style:italic&#34;&gt;:frame&lt;/span&gt; 0
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;   &lt;span style=&#34;color:#666;font-style:italic&#34;&gt;:draw&lt;/span&gt; &lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;draw-anim&lt;/span&gt;})
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;(&lt;span style=&#34;font-weight:bold;font-style:italic&#34;&gt;local &lt;/span&gt;&lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;jump-anim&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  {&lt;span style=&#34;color:#666;font-style:italic&#34;&gt;:start&lt;/span&gt; 288
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;   &lt;span style=&#34;color:#666;font-style:italic&#34;&gt;:end&lt;/span&gt; 302
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;   &lt;span style=&#34;color:#666;font-style:italic&#34;&gt;:frames&lt;/span&gt; 8
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;   &lt;span style=&#34;color:#666;font-style:italic&#34;&gt;:frame&lt;/span&gt; 0
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;   &lt;span style=&#34;color:#666;font-style:italic&#34;&gt;:draw&lt;/span&gt; &lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;draw-anim&lt;/span&gt;})
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#888;font-style:italic&#34;&gt;;; etc&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;In the &lt;code&gt;player.draw&lt;/code&gt; method I call this depending on the player&amp;rsquo;s state:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-fennel&#34; data-lang=&#34;fennel&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;(&lt;span style=&#34;font-weight:bold;font-style:italic&#34;&gt;fn &lt;/span&gt;&lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;player.draw&lt;/span&gt; [&lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;self&lt;/span&gt;]
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  (&lt;span style=&#34;font-weight:bold&#34;&gt;let &lt;/span&gt;[(&lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;x&lt;/span&gt; &lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;y&lt;/span&gt;) (&lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;camera&lt;/span&gt;&lt;span style=&#34;color:#666;font-style:italic&#34;&gt;:translate&lt;/span&gt; &lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;self.x&lt;/span&gt; &lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;self.y&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;flip?&lt;/span&gt; (&lt;span style=&#34;font-weight:bold&#34;&gt;if &lt;/span&gt;(&lt;span style=&#34;font-weight:bold&#34;&gt;&amp;lt; &lt;/span&gt;&lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;self.flip&lt;/span&gt; 0) 1 0)]
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    (&lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;echoes&lt;/span&gt;&lt;span style=&#34;color:#666;font-style:italic&#34;&gt;:draw&lt;/span&gt; &lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;self&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    (&lt;span style=&#34;font-weight:bold&#34;&gt;if &lt;/span&gt;(&lt;span style=&#34;font-weight:bold&#34;&gt;&amp;lt;= &lt;/span&gt;0 (&lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;m/abs&lt;/span&gt; &lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;self.xvel&lt;/span&gt;) 0.01)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        (&lt;span style=&#34;font-weight:bold&#34;&gt;if &lt;/span&gt;(&lt;span style=&#34;font-weight:bold&#34;&gt;not= &lt;/span&gt;0 &lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;self.yvel&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            (&lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;slow-jump-anim&lt;/span&gt;&lt;span style=&#34;color:#666;font-style:italic&#34;&gt;:draw&lt;/span&gt; &lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;x&lt;/span&gt; &lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;y&lt;/span&gt; &lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;flip?&lt;/span&gt; &lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;player&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            (&lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;idle-anim&lt;/span&gt;&lt;span style=&#34;color:#666;font-style:italic&#34;&gt;:draw&lt;/span&gt; &lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;x&lt;/span&gt; &lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;y&lt;/span&gt; &lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;flip?&lt;/span&gt; 60))
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        (&lt;span style=&#34;font-weight:bold&#34;&gt;not &lt;/span&gt;(&lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;grounded?&lt;/span&gt; &lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;self&lt;/span&gt;))
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        (&lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;case&lt;/span&gt; (&lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;wall-bound?&lt;/span&gt; &lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;player&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;          1 (&lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;spr&lt;/span&gt; 256 &lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;x&lt;/span&gt; &lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;y&lt;/span&gt; 0 1 1 0 1 2)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;          -1 (&lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;spr&lt;/span&gt; 256 &lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;x&lt;/span&gt; &lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;y&lt;/span&gt; 0 1 0 0 1 2)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;          &lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;_&lt;/span&gt; (&lt;span style=&#34;font-weight:bold&#34;&gt;if &lt;/span&gt;(&lt;span style=&#34;font-weight:bold&#34;&gt;&amp;gt; &lt;/span&gt;(&lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;m/abs&lt;/span&gt; &lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;self.xvel&lt;/span&gt;) 3)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                (&lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;jump-anim&lt;/span&gt;&lt;span style=&#34;color:#666;font-style:italic&#34;&gt;:draw&lt;/span&gt; &lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;x&lt;/span&gt; &lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;y&lt;/span&gt; &lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;flip?&lt;/span&gt; (&lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;m/floor&lt;/span&gt; (&lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;lerp&lt;/span&gt; 10 1 (&lt;span style=&#34;font-weight:bold&#34;&gt;/ &lt;/span&gt;(&lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;m/abs&lt;/span&gt; &lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;self.xvel&lt;/span&gt;) (&lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;m/abs&lt;/span&gt; &lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;self.max-xvel&lt;/span&gt;)))))
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                (&lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;slow-jump-anim&lt;/span&gt;&lt;span style=&#34;color:#666;font-style:italic&#34;&gt;:draw&lt;/span&gt; &lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;x&lt;/span&gt; &lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;y&lt;/span&gt; &lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;flip?&lt;/span&gt; &lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;player&lt;/span&gt;)))
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        (&lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;run-anim&lt;/span&gt;&lt;span style=&#34;color:#666;font-style:italic&#34;&gt;:draw&lt;/span&gt; &lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;x&lt;/span&gt; &lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;y&lt;/span&gt; &lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;flip?&lt;/span&gt; (&lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;m/floor&lt;/span&gt; (&lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;lerp&lt;/span&gt; 10 3 (&lt;span style=&#34;font-weight:bold&#34;&gt;/ &lt;/span&gt;(&lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;m/abs&lt;/span&gt; &lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;self.xvel&lt;/span&gt;) (&lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;m/abs&lt;/span&gt; &lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;self.max-xvel&lt;/span&gt;))))))))
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;As you can see, I don&amp;rsquo;t yet have a state machine or anything like it in my player object, which I fixed in later days.
However, this was enough to make the animations you&amp;rsquo;ve seen in the video.&lt;/p&gt;
&lt;p&gt;Animation speed is determined by the &lt;code&gt;speed&lt;/code&gt; parameter, which is named incorrectly, as it is more of an update interval in frames.
I use linear interpolation to play animations faster when player&amp;rsquo;s speed increases, interpolating from &lt;code&gt;10&lt;/code&gt; tics to &lt;code&gt;1&lt;/code&gt; tic.
Each frame is &lt;code&gt;60&lt;/code&gt; tics.&lt;/p&gt;
&lt;h2 id=&#34;day-2&#34;&gt;Day 2&lt;/h2&gt;
&lt;p&gt;Second day I focused on improving animations, as currently they look very janky.&lt;/p&gt;
&lt;p&gt;I have Aseprite installed, so I picked up my drawing tablet and sat down working on the run animation.
Or I would have done that if Wacom wasn&amp;rsquo;t a piece of crap.&lt;/p&gt;
&lt;p&gt;I had to install drivers, but not just any drivers - the newest drivers are incompatible with my rather old tablet.
They are quite big size-wise, and with slow internet it was a pain, since I blindly downloaded the latest driver, and was met with a message, that I should upgrade my device.
Luckily, the official web page still has a working driver for my OS and tablet combination, but I was afraid for a second that I would have to resort to using my phone.
Not that it is a problem, but I wanted to try Aseprite for this project.&lt;/p&gt;
&lt;p&gt;The result is as follows:&lt;/p&gt;
&lt;p&gt;&lt;video autoplay loop muted&gt;&lt;source src=&#34;https://andreyor.st/2026-05-24-spring-lisp-game-jam-2026/day-2.mp4&#34; type=&#34;video/mp4&#34;&gt;
Your browser does not support the video tag.&lt;/video&gt;&lt;/p&gt;
&lt;p&gt;I also added sliding animation, and spikes, as no platformer is complete without spikes, am I right or am I right?&lt;/p&gt;
&lt;p&gt;Jumping also has two states now - a slow jump, and a fast jump, each with their own animation.
For slow jumps the player sprite is mostly static, changing only when the vertical velocity goes from negative to positive.
When a certain amount of speed is reached, however, the jump changes into a Salto Mortale mode, where the player spins frantically, like Samus Aran.
Did I mention that Metroid was a huge inspiration for me this time around?&lt;/p&gt;
&lt;p&gt;In addition to sliding, I added wall slide, which is not only a visual aid, but also slows you down when falling a bit.&lt;/p&gt;
&lt;h2 id=&#34;day-3&#34;&gt;Day 3&lt;/h2&gt;
&lt;p&gt;I felt satisfied with my player controller so far, so I started working on the level itself.
This includes decorations, and level design in general.&lt;/p&gt;
&lt;p&gt;Here&amp;rsquo;s what I got by the end of day three:&lt;/p&gt;
&lt;p&gt;&lt;video autoplay loop muted&gt;&lt;source src=&#34;https://andreyor.st/2026-05-24-spring-lisp-game-jam-2026/day-3.mp4&#34; type=&#34;video/mp4&#34;&gt;
Your browser does not support the video tag.&lt;/video&gt;&lt;/p&gt;
&lt;p&gt;I designed a few tiles for the ground and walls, as well as variants for different biomes:&lt;/p&gt;
&lt;figure&gt;&lt;img src=&#34;https://andreyor.st/2026-05-24-spring-lisp-game-jam-2026/day-3-tiles.png&#34;/&gt;
&lt;/figure&gt;

&lt;p&gt;It&amp;rsquo;s hard to show the whole map in TIC, so here&amp;rsquo;s a screenshot from the built-in overview:&lt;/p&gt;
&lt;figure&gt;&lt;img src=&#34;https://andreyor.st/2026-05-24-spring-lisp-game-jam-2026/day-3-map.png&#34;/&gt;
&lt;/figure&gt;

&lt;p&gt;Aesthetics for this game were inspired by the palette I&amp;rsquo;ve chosen, and by the game Animal Well, which I&amp;rsquo;ve played recently.
Or at least I wanted to think that way, as I liked the artstyle, and limited resolution of that game.&lt;/p&gt;
&lt;h2 id=&#34;days-4-7&#34;&gt;Days 4-7&lt;/h2&gt;
&lt;p&gt;The rest of the days I&amp;rsquo;ve been working on the level design.
I even started to plan out the level on a piece of paper, like so:&lt;/p&gt;
&lt;figure&gt;&lt;img src=&#34;https://andreyor.st/2026-05-24-spring-lisp-game-jam-2026/plan.jpg&#34;/&gt;
&lt;/figure&gt;

&lt;p&gt;Here&amp;rsquo;s map progress by day:&lt;/p&gt;
&lt;figure&gt;&lt;img src=&#34;https://andreyor.st/2026-05-24-spring-lisp-game-jam-2026/day-4.png&#34;
         alt=&#34;Figure 1: Day 4&#34;/&gt;&lt;figcaption&gt;
            &lt;p&gt;&lt;span class=&#34;figure-number&#34;&gt;Figure 1: &lt;/span&gt;Day 4&lt;/p&gt;
        &lt;/figcaption&gt;
&lt;/figure&gt;

&lt;figure&gt;&lt;img src=&#34;https://andreyor.st/2026-05-24-spring-lisp-game-jam-2026/day-4-2.png&#34;
         alt=&#34;Figure 2: Day 4, again&#34;/&gt;&lt;figcaption&gt;
            &lt;p&gt;&lt;span class=&#34;figure-number&#34;&gt;Figure 2: &lt;/span&gt;Day 4, again&lt;/p&gt;
        &lt;/figcaption&gt;
&lt;/figure&gt;

&lt;figure&gt;&lt;img src=&#34;https://andreyor.st/2026-05-24-spring-lisp-game-jam-2026/day-5.png&#34;
         alt=&#34;Figure 3: Day 5&#34;/&gt;&lt;figcaption&gt;
            &lt;p&gt;&lt;span class=&#34;figure-number&#34;&gt;Figure 3: &lt;/span&gt;Day 5&lt;/p&gt;
        &lt;/figcaption&gt;
&lt;/figure&gt;

&lt;figure&gt;&lt;img src=&#34;https://andreyor.st/2026-05-24-spring-lisp-game-jam-2026/day-6.png&#34;
         alt=&#34;Figure 4: Day 6&#34;/&gt;&lt;figcaption&gt;
            &lt;p&gt;&lt;span class=&#34;figure-number&#34;&gt;Figure 4: &lt;/span&gt;Day 6&lt;/p&gt;
        &lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p&gt;At some point I completed the paper plan:&lt;/p&gt;
&lt;figure&gt;&lt;img src=&#34;https://andreyor.st/2026-05-24-spring-lisp-game-jam-2026/full-plan.jpg&#34;/&gt;
&lt;/figure&gt;

&lt;figure&gt;&lt;img src=&#34;https://andreyor.st/2026-05-24-spring-lisp-game-jam-2026/day-7.png&#34;
         alt=&#34;Figure 5: Day 7&#34;/&gt;&lt;figcaption&gt;
            &lt;p&gt;&lt;span class=&#34;figure-number&#34;&gt;Figure 5: &lt;/span&gt;Day 7&lt;/p&gt;
        &lt;/figcaption&gt;
&lt;/figure&gt;

&lt;figure&gt;&lt;img src=&#34;https://andreyor.st/2026-05-24-spring-lisp-game-jam-2026/day-8.png&#34;
         alt=&#34;Figure 6: Day 8&#34;/&gt;&lt;figcaption&gt;
            &lt;p&gt;&lt;span class=&#34;figure-number&#34;&gt;Figure 6: &lt;/span&gt;Day 8&lt;/p&gt;
        &lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p&gt;I was ready to start working on the last bit of the map, when I realized that I made a mistake.
My plan contained an extra row on top, when in reality I had just two more.&lt;/p&gt;
&lt;p&gt;This meant that I had to trim down the last section, as I couldn&amp;rsquo;t really throw anything from the middle section, and it wouldn&amp;rsquo;t make sense to make the top section penetrate down.&lt;/p&gt;
&lt;figure&gt;&lt;img src=&#34;https://andreyor.st/2026-05-24-spring-lisp-game-jam-2026/day-9.png&#34;
         alt=&#34;Figure 7: Day 9&#34;/&gt;&lt;figcaption&gt;
            &lt;p&gt;&lt;span class=&#34;figure-number&#34;&gt;Figure 7: &lt;/span&gt;Day 9&lt;/p&gt;
        &lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p&gt;Still, I had the full map complete by day nine, out of ten days available for the jam.
A bit of a close call, but I could release this game any time, as it is essentially complete now.&lt;/p&gt;
&lt;p&gt;During these days I also made a main menu screen, and the end screen.
The end screen feels a bit barebones, but I had no time to do anything more with it, as I still had to do the most difficult part - sounds and music.&lt;/p&gt;
&lt;h2 id=&#34;sfx&#34;&gt;SFX&lt;/h2&gt;
&lt;p&gt;Sound effects weren&amp;rsquo;t as hard, when it comes to the player itself.
I&amp;rsquo;m using simple effects for steps, jumps, and slides based on white noise that TIC provides.
Timing them to animation frames is easy enough, as I can call &lt;code&gt;sfx&lt;/code&gt; from the animation pipeline itself.&lt;/p&gt;
&lt;p&gt;Because of that, things like steps are actually in sync with the animation, which I like.
However, in the browser the sound may lag, I don&amp;rsquo;t really know why, but it did for all my previous games.
Maybe it also depends on the browser.&lt;/p&gt;
&lt;h2 id=&#34;day-10-music&#34;&gt;Day 10 - Music&lt;/h2&gt;
&lt;p&gt;As I usually do, I left the hardest part to the last day.
If I fail at it, I won&amp;rsquo;t include it in the game, and release it as is - it&amp;rsquo;s still a complete game.
But, if I succeed, the game will be a bit better.&lt;/p&gt;
&lt;p&gt;Music composition is not of my top skills, I&amp;rsquo;d say I&amp;rsquo;m pretty bad at it.
There are few reasons for that, first and foremost being it&amp;rsquo;s so clunky to do that on a computer.
If I had a MIDI keyboard, or an iPad with GarageBand, I think I would spend more time doing that.&lt;/p&gt;
&lt;p&gt;So I reached out to &lt;a href=&#34;https://whoisthatdf.bandcamp.com/&#34; target=&#34;_blank&#34;&gt;a friend&lt;/a&gt; with whom we did a &lt;a href=&#34;https://www.youtube.com/watch?v=y9Mt3EJ6d4k&#34; target=&#34;_blank&#34;&gt;collaboration&lt;/a&gt; before, and asked them to help me out.
They were kind enough to provide me with an original piece, which I had to port to TIC with the best of my ability.&lt;/p&gt;
&lt;p&gt;TIC&amp;rsquo;s music tracker is a bit weird, and very limiting for someone who&amp;rsquo;s not versatile enough with trackers in general.&lt;/p&gt;
&lt;p&gt;Adapting the song was quite a challenge for me.
First of all, the song was written in non-tracker software, as MIDI.
Having a piano-roll view of it helped with adapting, but some of the effects that were used were hard to recreate, so I did my best.&lt;/p&gt;
&lt;p&gt;For example, delay.
I had to create a wave that modulated its volume to act like its two notes repeating after a short delay.&lt;/p&gt;
&lt;figure&gt;&lt;img src=&#34;https://andreyor.st/2026-05-24-spring-lisp-game-jam-2026/sfx.png&#34;/&gt;
&lt;/figure&gt;

&lt;p&gt;Then there were drums.
I need to mention that I&amp;rsquo;m limited by three channels out of four available.
One channel is reserved for in-game sound effects, like steps, jumps, powerups, etc.
This leaves me with three channels for music.&lt;/p&gt;
&lt;figure&gt;&lt;img src=&#34;https://andreyor.st/2026-05-24-spring-lisp-game-jam-2026/tracker.png&#34;/&gt;
&lt;/figure&gt;

&lt;p&gt;I used the first channel for the lead melody, second one for the bass, and the third one for the drum beat.
Drums proved that it&amp;rsquo;s not really feasible to do them in one channel.
You can&amp;rsquo;t overlay two samples, like in a modern MIDI editor.
So you have to choose between kick and snare.
Or snare and cymbal.
Or kick and cymbal.
You get the idea.&lt;/p&gt;
&lt;h2 id=&#34;the-game&#34;&gt;The game&lt;/h2&gt;
&lt;p&gt;Anyway, this concludes this dev log, and the game is now available on &lt;a href=&#34;https://andreyorst.itch.io/speed&#34; target=&#34;_blank&#34;&gt;itch.io&lt;/a&gt;.
If you like it, consider voting for it on the &lt;a href=&#34;https://itch.io/jam/spring-lisp-game-jam-2026/rate/4606269&#34; target=&#34;_blank&#34;&gt;jam&amp;rsquo;s page&lt;/a&gt;.
And if you wish to download it, here&amp;rsquo;s the cart:&lt;/p&gt;
&lt;figure class=&#34;pixelart&#34;&gt;&lt;img src=&#34;https://andreyor.st/2026-05-24-spring-lisp-game-jam-2026/speed.png&#34;/&gt;
&lt;/figure&gt;

&lt;p&gt;I can complete this game in about 10 minutes, but I think it&amp;rsquo;ll take a fair bit more time for players unfamiliar with it.
After all, I spent a lot of time playing this game during development, and I know it pretty well.&lt;/p&gt;
&lt;p&gt;I know that some levels in this game are probably very janky.
Many jumps are probably not as obvious as they should have been, or unnecessarily hard.
I&amp;rsquo;ve had no time to get this game play-tested by other people, so that&amp;rsquo;s that.
Still, I fixed the most annoying places that drove even me nuts.
And overall, I&amp;rsquo;m satisfied with what I made.&lt;/p&gt;
&lt;p&gt;I hope you like this game, but don&amp;rsquo;t forget to check other submissions too!
If you&amp;rsquo;re a fellow participant, I wish you luck, and let&amp;rsquo;s see who&amp;rsquo;s gonna be the winner!
And if not, I encourage you to try it next year.
It&amp;rsquo;s fun :)&lt;/p&gt;
&lt;div class=&#34;comment-link&#34;&gt; &lt;a href=&#34;mailto:%61%6e%64%72%65%79%6f%72%73%74%2b%62%6c%6f%67%40%67%6d%61%69%6c%2e%63%6f%6d?subject=Comment: Spring Lisp Game Jam 2026&#34; target=&#34;_blank&#34; rel=&#34;noopener noreferrer&#34;&gt;Comment via email&lt;/a&gt;&lt;/div&gt;</description>
      <pubDate>Sun, 24 May 2026 08:08:00 +0300</pubDate>
    </item><item>
      <title>Slopes in AABB collision systems</title>
      <link>https://andreyor.st/posts/2026-01-09-slopes-in-aabb-collision-systems/</link>
      <guid>https://andreyor.st/posts/2026-01-09-slopes-in-aabb-collision-systems/</guid>
      <description>&lt;p&gt;Recently (again, &lt;a href=&#34;https://andreyor.st/posts/2024-09-08-boredom-and-gamedev/&#34;&gt;bored on a vacation&lt;/a&gt;), I started working on a game I&amp;rsquo;ve planned for a long time.
I wasn&amp;rsquo;t satisfied with my &lt;a href=&#34;https://tic80.com/play?cart=3473&#34; target=&#34;_blank&#34;&gt;existing implementations&lt;/a&gt; of a player controller&lt;sup id=&#34;fnref:1&#34;&gt;&lt;a href=&#34;#fn:1&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;1&lt;/a&gt;&lt;/sup&gt;, so I started working on a new one.
After a bit of fiddling around, I came to something I&amp;rsquo;m satisfied with, for now, at least, but while working on it, I wanted to add something I haven&amp;rsquo;t done in any of my projects yet - I decided to add slopes to my game.&lt;/p&gt;
&lt;p&gt;I&amp;rsquo;m no game developer, to be honest, it&amp;rsquo;s not even my hobby, although I enjoy the process to some extent.
Game development offers immediate visual and interactive feedback, unlike many other areas in programming, and I like that.
So, while working on this player controller and figuring out how to implement slopes, I&amp;rsquo;ve searched the web to see if someone already did something similar in a system I&amp;rsquo;m working with, but I didn&amp;rsquo;t manage to find anything.
Maybe I simply wasn&amp;rsquo;t specific enough in my search requests, or didn&amp;rsquo;t search deep enough, and there&amp;rsquo;s actually a guide on how to do that somewhere on the web, I decided to write this post anyway.&lt;/p&gt;
&lt;h2 id=&#34;aabb-collisions&#34;&gt;AABB collisions&lt;/h2&gt;
&lt;p&gt;First, let&amp;rsquo;s discuss what AABB actually means.&lt;/p&gt;
&lt;p&gt;For those unfamiliar with collision detection, there are many ways of checking if two objects collide.
A lot of them, actually, each with its pros and cons.
Some give very accurate collisions for objects of any shape, but are not real-time viable, others are real-time, but far less accurate.
It&amp;rsquo;s an ongoing research, and new ways of detecting complex collisions as fast as possible &lt;a href=&#34;https://www.youtube.com/watch?v=2c8o65JiPQY&amp;amp;pp=ygURb25lIG1pbnV0ZSBwYXBlcnM%3D&#34; target=&#34;_blank&#34;&gt;appear every now and then&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;I&amp;rsquo;m mainly developing games in either TIC-80 or LÖVE2D.
LÖVE2D actually comes with a collision detection library, called &lt;a href=&#34;https://box2d.org/about/&#34; target=&#34;_blank&#34;&gt;Box2D&lt;/a&gt;.
My games are sprite-based, and since I often use TIC-80, I can&amp;rsquo;t really commit to Box2D, so instead I opt for a simpler collision detection logic.&lt;/p&gt;
&lt;p&gt;That&amp;rsquo;s where AABB comes - it&amp;rsquo;s an Axis-Sligned Bounding Box collision detection system:&lt;/p&gt;
&lt;figure class=&#34;invertable&#34;&gt;&lt;img src=&#34;https://andreyor.st/2026-01-09-slopes-in-aabb-collision-systems/aabb.png&#34;
         alt=&#34;Figure 1: Axis-aligned rectangles are quite easy to check for intersections, since all we need to know is their origin and dimensions.&#34;/&gt;&lt;figcaption&gt;
            &lt;p&gt;&lt;span class=&#34;figure-number&#34;&gt;Figure 1: &lt;/span&gt;Axis-aligned rectangles are quite easy to check for intersections, since all we need to know is their origin and dimensions.&lt;/p&gt;
        &lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p&gt;The general idea is that if the bounding box of object A is completely to the left or to the right of object B, the objects don&amp;rsquo;t collide.
Similarly, if the object A is above or below object B, there&amp;rsquo;s no collision either.
And when all four of these situations are not true, then there&amp;rsquo;s a collision.&lt;/p&gt;
&lt;p&gt;It&amp;rsquo;s a rather simple solution, but it can yield satisfying results.
Implementing this by hand is trivial, but there&amp;rsquo;s a lot more to checking collisions - we need to make it quite optimized, and handle object interactions.&lt;/p&gt;
&lt;p&gt;Thankfully, there&amp;rsquo;s an amazing pure Lua library called &lt;a href=&#34;https://github.com/kikito/bump.lua&#34; target=&#34;_blank&#34;&gt;bump.lua&lt;/a&gt;, that implements these AABB checks and more.
And it&amp;rsquo;s near perfect for sprite-based games, since everything is aligned to the grid, so that&amp;rsquo;s what I&amp;rsquo;m using in TIC-80 and Love2D.
Perfect, until we want something other than a rectangle.
For example, slopes.&lt;/p&gt;
&lt;h2 id=&#34;implementing-slopes-with-bump-dot-lua&#34;&gt;Implementing slopes with &lt;code&gt;bump.lua&lt;/code&gt;&lt;/h2&gt;
&lt;p&gt;To be honest, there&amp;rsquo;s not that much to it for implementing slopes in an AABB collision system.
Actually, I could just give you the formula and be done with it.
Even more, actually - I&amp;rsquo;m sure you can figure it out if you think about it for just a bit.
But that&amp;rsquo;s not fun!
So instead, I&amp;rsquo;ll walk you through from the beginning - creating a TIC project, adding &lt;code&gt;bump.lua&lt;/code&gt;, loading entities to the world, adding collisions, simple physics, and finally, slopes.&lt;/p&gt;
&lt;p&gt;But, if you want, you can skip directly to the &lt;a href=&#34;#implementing-slopes-with-aabb-collisions&#34;&gt;slope implementation section&lt;/a&gt;, no worries!&lt;/p&gt;
&lt;h3 id=&#34;tic-80&#34;&gt;TIC-80&lt;/h3&gt;
&lt;p&gt;&lt;a href=&#34;https://tic80.com/&#34; target=&#34;_blank&#34;&gt;TIC-80&lt;/a&gt; is a small game engine that supports Lua as one of its main languages to develop games, along with other ones, like &lt;a href=&#34;https://fennel-lang.org/&#34; target=&#34;_blank&#34;&gt;Fennel&lt;/a&gt;.
I primarily do my projects in Fennel, as I like it a little bit more than Lua&lt;sup id=&#34;fnref:2&#34;&gt;&lt;a href=&#34;#fn:2&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;2&lt;/a&gt;&lt;/sup&gt;, but for this post, I chose Lua, as I want this to be a more general tutorial, applicable to a wider audience.&lt;/p&gt;
&lt;p&gt;Even though TIC-80 is a fantasy computer, I believe it is capable of being an engine that you can use to make full-fledged games with.
Two years ago, &lt;a href=&#34;https://www.playbalatro.com/&#34; target=&#34;_blank&#34;&gt;Balatro&lt;/a&gt; again proved that Love2D is fully suited for making massively successful games, without the need for something more, like Godot or Unity.
I think TIC-80 can shine &lt;a href=&#34;https://tic80.com/play?cart=4496&#34; target=&#34;_blank&#34;&gt;similarly&lt;/a&gt;, but it seems that far fewer people consider it as a viable alternative, even fewer than with Love2D.&lt;/p&gt;
&lt;p&gt;The other thing I often think about is that games don&amp;rsquo;t have to be implemented with the best technology to be &lt;em&gt;good games&lt;/em&gt;.
I&amp;rsquo;ve played a few games on Game Boy Color and especially Game Boy Advance, and they were amazing even today.
And these consoles were quite limited even for the time.
Yet, people are still making games for them.&lt;/p&gt;
&lt;p&gt;And that&amp;rsquo;s another reason why I want to write this post more as a tutorial on TIC-80, as it can maybe make you want to try it!&lt;/p&gt;
&lt;p&gt;So let&amp;rsquo;s start by firing up TIC-80:&lt;/p&gt;
&lt;figure&gt;&lt;img src=&#34;https://andreyor.st/2026-01-09-slopes-in-aabb-collision-systems/tic80.png&#34;/&gt;
&lt;/figure&gt;

&lt;p&gt;By default, TIC-80 starts you in a console-like environment.
It&amp;rsquo;s a fantasy &lt;em&gt;computer&lt;/em&gt;, after all.&lt;/p&gt;
&lt;p&gt;By pressing &lt;kbd&gt;Esc&lt;/kbd&gt;, you can go into the code editor:&lt;/p&gt;
&lt;figure&gt;&lt;img src=&#34;https://andreyor.st/2026-01-09-slopes-in-aabb-collision-systems/tic80-code-editor.png&#34;/&gt;
&lt;/figure&gt;

&lt;p&gt;You can also switch to a built-in sprite editor by pressing &lt;kbd&gt;F2&lt;/kbd&gt; or clicking on a ghost icon in the top bar:&lt;/p&gt;
&lt;figure&gt;&lt;img src=&#34;https://andreyor.st/2026-01-09-slopes-in-aabb-collision-systems/tic80-sprite-editor.png&#34;/&gt;
&lt;/figure&gt;

&lt;p&gt;There&amp;rsquo;s also a map editor, which we&amp;rsquo;ll look into a bit later, you can create sound effects with a built-in synthesizer and write music in the tracker!
It&amp;rsquo;s a fully self-contained system that you can develop your games in, and not just games - people write music in it, create beautiful &lt;a href=&#34;https://tic80.com/play?cat=5&amp;amp;sort=2&#34; target=&#34;_blank&#34;&gt;entries&lt;/a&gt; for Demo Scene contests, and more.&lt;/p&gt;
&lt;p&gt;I won&amp;rsquo;t be using the inbuilt code editor, though, as I&amp;rsquo;m much more comfortable writing in Emacs, and you can use any external editor with TIC, but we&amp;rsquo;ll be using the sprite and map editors.
So, let&amp;rsquo;s start!&lt;/p&gt;
&lt;h3 id=&#34;creating-a-project-and-adding-bump-dot-lua&#34;&gt;Creating a project and adding &lt;code&gt;bump.lua&lt;/code&gt;&lt;/h3&gt;
&lt;p&gt;Actually, if you did start the TIC-80 with me, you don&amp;rsquo;t have to create anything - by default, TIC creates a sample cart that you can edit right away.
That&amp;rsquo;s what we&amp;rsquo;ll do.
First, let&amp;rsquo;s edit the metadata at the top of the cart:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-lua&#34; data-lang=&#34;lua&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#888;font-style:italic&#34;&gt;-- title:   AABB Slopes&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#888;font-style:italic&#34;&gt;-- author:  Andrey Listopadov&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#888;font-style:italic&#34;&gt;-- desc:    Making slopes wiht AABB collisions&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#888;font-style:italic&#34;&gt;-- site:    https://andreyor.st&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#888;font-style:italic&#34;&gt;-- license: MIT License&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#888;font-style:italic&#34;&gt;-- version: 0.1&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#888;font-style:italic&#34;&gt;-- script:  lua&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;We could also delete default sprites right now, but we&amp;rsquo;ll do it later.
Right now, we need to add &lt;code&gt;bump.lua&lt;/code&gt; to the cart.&lt;/p&gt;
&lt;p&gt;TIC-80 doesn&amp;rsquo;t really provide access to your filesystem - it is expected for the cart to contain everything, much like game cartridges did in the early days of home consoles.
Because of that, it&amp;rsquo;s not possible to install &lt;code&gt;bump.lua&lt;/code&gt; via LuaRocks, or download it and place the file near your cart.
We&amp;rsquo;ll have to embed it directly.&lt;/p&gt;
&lt;p&gt;Luckily for us, it&amp;rsquo;s really easy to do so in Lua!
All we need is to set &lt;code&gt;package.preload.bump&lt;/code&gt; to a function that contains &lt;a href=&#34;https://raw.githubusercontent.com/kikito/bump.lua/refs/heads/master/bump.lua&#34; target=&#34;_blank&#34;&gt;all of the library code&lt;/a&gt;.
And even greater, that &lt;code&gt;bump.lua&lt;/code&gt; is a simple, single-file, self-contained Lua library.&lt;/p&gt;
&lt;p&gt;So we can do it like so:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-lua&#34; data-lang=&#34;lua&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;package.preload.bump = &lt;span style=&#34;font-weight:bold&#34;&gt;function&lt;/span&gt;()
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#888;font-style:italic&#34;&gt;-- Copy the bump.lua file contens here.&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#888;font-style:italic&#34;&gt;-- Yes, as is.&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;font-weight:bold&#34;&gt;end&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Now, we can use &lt;code&gt;require&lt;/code&gt; to bring this library to our game, and create the world:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-lua&#34; data-lang=&#34;lua&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;font-weight:bold;font-style:italic&#34;&gt;local&lt;/span&gt; bump = require &lt;span style=&#34;color:#666;font-style:italic&#34;&gt;&amp;#34;bump&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;font-weight:bold;font-style:italic&#34;&gt;local&lt;/span&gt; world = bump.newWorld(24)
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Great! Now let&amp;rsquo;s draw some sprites we&amp;rsquo;ll use!&lt;/p&gt;
&lt;h3 id=&#34;creating-sprites-and-using-the-map-editor&#34;&gt;Creating sprites and using the map editor&lt;/h3&gt;
&lt;p&gt;There&amp;rsquo;s nothing much to talk about here, as TIC features a rather simplistic sprite editor:&lt;/p&gt;
&lt;figure&gt;&lt;img src=&#34;https://andreyor.st/2026-01-09-slopes-in-aabb-collision-systems/sprites.png&#34;/&gt;
&lt;/figure&gt;

&lt;p&gt;However, we&amp;rsquo;ll be using some of its advanced features, which can be revealed by pressing this switch in the top left corner of the editor:&lt;/p&gt;
&lt;figure&gt;&lt;img src=&#34;https://andreyor.st/2026-01-09-slopes-in-aabb-collision-systems/sprites-advanced.png&#34;
         alt=&#34;Figure 2: Advanced mode switch (red arrow), flags (green arrow)&#34; width=&#34;50%&#34;/&gt;&lt;figcaption&gt;
            &lt;p&gt;&lt;span class=&#34;figure-number&#34;&gt;Figure 2: &lt;/span&gt;Advanced mode switch (red arrow), flags (green arrow)&lt;/p&gt;
        &lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p&gt;One of such features is the flags we can set on a sprite.
These flags act as a bit mask, so you can set up to 256 combinations of flags, which is enough, since TIC has space for exactly 256 sprites.
Did I mention how cool the design of TIC is yet?&lt;/p&gt;
&lt;p&gt;I&amp;rsquo;ve set bit 1 here to indicate that this sprite is a ball, and I set bit 0 for all of the solid objects drawn near it.
This way, when we load these sprites to the game world, we&amp;rsquo;ve created with &lt;code&gt;bump.lua&lt;/code&gt;, we will be able determine what this object should be.&lt;/p&gt;
&lt;p&gt;Now, let&amp;rsquo;s use these sprites in the map editor:&lt;/p&gt;
&lt;figure&gt;&lt;img src=&#34;https://andreyor.st/2026-01-09-slopes-in-aabb-collision-systems/map-editor.png&#34;/&gt;
&lt;/figure&gt;

&lt;p&gt;In the map editor, you can choose from either 256 tiles or 256 sprites to draw your map with.
TIC actually has two sprite-sets: tiles and sprites.
The only distinction between them is that you&amp;rsquo;re limited to either of those sets when you design your map.
Other than that, they&amp;rsquo;re the same.&lt;/p&gt;
&lt;p&gt;I&amp;rsquo;ve created a small test scene, with our ball and some walls:&lt;/p&gt;
&lt;figure&gt;&lt;img src=&#34;https://andreyor.st/2026-01-09-slopes-in-aabb-collision-systems/map.png&#34;/&gt;
&lt;/figure&gt;

&lt;p&gt;Now we can start implementing our game logic, as right now if we run this cart, nothing would happen yet.&lt;/p&gt;
&lt;h3 id=&#34;loading-entities-to-the-world&#34;&gt;Loading entities to the world&lt;/h3&gt;
&lt;p&gt;We&amp;rsquo;ll start by editing the &lt;code&gt;TIC&lt;/code&gt; function in our standard cart:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-lua&#34; data-lang=&#34;lua&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;font-weight:bold&#34;&gt;function&lt;/span&gt; &lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;TIC&lt;/span&gt;()
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    cls(0)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    map(0, 0, 31, 18, 0, 0, 0, 1)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;font-weight:bold&#34;&gt;end&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Now, if we run our cart, we&amp;rsquo;ll see the same thing we&amp;rsquo;ve seen in the map editor:&lt;/p&gt;
&lt;figure&gt;&lt;img src=&#34;https://andreyor.st/2026-01-09-slopes-in-aabb-collision-systems/first-run.png&#34;/&gt;
&lt;/figure&gt;

&lt;p&gt;However, nothing moves yet, as we haven&amp;rsquo;t implemented any of the game&amp;rsquo;s logic.
Before we can do that, we&amp;rsquo;ll need to populate our world with rectangles, so let&amp;rsquo;s do that.&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-lua&#34; data-lang=&#34;lua&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;font-weight:bold;font-style:italic&#34;&gt;local&lt;/span&gt; &lt;span style=&#34;font-weight:bold&#34;&gt;function&lt;/span&gt; &lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;load_map&lt;/span&gt;()
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;font-weight:bold&#34;&gt;for&lt;/span&gt; x=0, 240 * 8, 8 &lt;span style=&#34;font-weight:bold&#34;&gt;do&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;font-weight:bold&#34;&gt;for&lt;/span&gt; y=0, 136 * 8, 8 &lt;span style=&#34;font-weight:bold&#34;&gt;do&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            &lt;span style=&#34;font-weight:bold;font-style:italic&#34;&gt;local&lt;/span&gt; obj = is_obj(x, y)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            &lt;span style=&#34;font-weight:bold&#34;&gt;if&lt;/span&gt; obj &lt;span style=&#34;font-weight:bold&#34;&gt;then&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                world:add(obj, obj.x, obj.y, obj.w, obj.h)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;font-weight:bold&#34;&gt;end&lt;/span&gt; &lt;span style=&#34;font-weight:bold&#34;&gt;end&lt;/span&gt; &lt;span style=&#34;font-weight:bold&#34;&gt;end&lt;/span&gt; &lt;span style=&#34;font-weight:bold&#34;&gt;end&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Here, I&amp;rsquo;m loading the &lt;strong&gt;entire&lt;/strong&gt; map to the world.
It&amp;rsquo;s not necessary to do that, since we&amp;rsquo;re only going to use one map screen, but it doesn&amp;rsquo;t hurt.
The key function here is the &lt;code&gt;is_obj&lt;/code&gt; predicate, which we&amp;rsquo;re going to write next:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-lua&#34; data-lang=&#34;lua&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;font-weight:bold;font-style:italic&#34;&gt;local&lt;/span&gt; balls = {}
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;font-weight:bold;font-style:italic&#34;&gt;local&lt;/span&gt; &lt;span style=&#34;font-weight:bold&#34;&gt;function&lt;/span&gt; &lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;is_obj&lt;/span&gt;(x, y)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;font-weight:bold;font-style:italic&#34;&gt;local&lt;/span&gt; id = id_at(x, y)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;font-weight:bold&#34;&gt;if&lt;/span&gt; is_solid(id) &lt;span style=&#34;font-weight:bold&#34;&gt;then&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;font-weight:bold&#34;&gt;return&lt;/span&gt; {x = x, y = y, h = 8, w = 8}
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;font-weight:bold&#34;&gt;elseif&lt;/span&gt; is_ball(id) &lt;span style=&#34;font-weight:bold&#34;&gt;then&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;font-weight:bold;font-style:italic&#34;&gt;local&lt;/span&gt; ball = {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            x = x,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            y = y,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            h = 8,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            w = 8,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            xvel = 0,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            yvel = 0,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            is_ball = &lt;span style=&#34;font-weight:bold&#34;&gt;true&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        balls[ball] = &lt;span style=&#34;font-weight:bold&#34;&gt;true&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;font-weight:bold&#34;&gt;return&lt;/span&gt; ball
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;font-weight:bold&#34;&gt;end&lt;/span&gt; &lt;span style=&#34;font-weight:bold&#34;&gt;end&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;First things first, we need to get the ID of the tile at coordinates &lt;code&gt;x&lt;/code&gt; and &lt;code&gt;y&lt;/code&gt;.
We do so by using TIC&amp;rsquo;s built-in function &lt;code&gt;mget&lt;/code&gt;:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-lua&#34; data-lang=&#34;lua&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;font-weight:bold;font-style:italic&#34;&gt;local&lt;/span&gt; &lt;span style=&#34;font-weight:bold&#34;&gt;function&lt;/span&gt; &lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;id_at&lt;/span&gt;(x, y)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;font-weight:bold&#34;&gt;return&lt;/span&gt; mget(x // 8, y // 8)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;font-weight:bold&#34;&gt;end&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Next, we&amp;rsquo;ll define a set of predicates that, given an &lt;code&gt;id&lt;/code&gt; will tell if the object we&amp;rsquo;re inspecting is a wall, or a ball:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-lua&#34; data-lang=&#34;lua&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;font-weight:bold;font-style:italic&#34;&gt;local&lt;/span&gt; &lt;span style=&#34;font-weight:bold&#34;&gt;function&lt;/span&gt; &lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;is_solid&lt;/span&gt;(id)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;font-weight:bold&#34;&gt;return&lt;/span&gt; fget(id, 0)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;font-weight:bold&#34;&gt;end&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;font-weight:bold;font-style:italic&#34;&gt;local&lt;/span&gt; &lt;span style=&#34;font-weight:bold&#34;&gt;function&lt;/span&gt; &lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;is_ball&lt;/span&gt;(id)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;font-weight:bold&#34;&gt;return&lt;/span&gt; fget(id, 1)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;font-weight:bold&#34;&gt;end&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;So the &lt;code&gt;is_obj&lt;/code&gt; function we&amp;rsquo;ve defined above gets the tile &lt;code&gt;id&lt;/code&gt;, and checks if it is a ball, or a wall tile.
If so, it creates an object that we can add to the &lt;code&gt;bump.lua&lt;/code&gt; world.
Each object has a set amount of required properties, but you can also have additional ones, like the &lt;code&gt;is_ball&lt;/code&gt; field, in the case of the ball object.
We also add a ball to the list of balls, in case we might want to add more than one ball later.&lt;/p&gt;
&lt;p&gt;Now, we can load the map.
We do so in the &lt;code&gt;BOOT&lt;/code&gt; function, which executes when the cart is being loaded.
You can also write the call at the top level if you want, and it may make the code a little bit more portable, as older versions of TIC didn&amp;rsquo;t have &lt;code&gt;BOOT&lt;/code&gt;, but I&amp;rsquo;m not targeting those, so:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-lua&#34; data-lang=&#34;lua&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;font-weight:bold&#34;&gt;function&lt;/span&gt; &lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;BOOT&lt;/span&gt;()
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    load_map()
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;font-weight:bold&#34;&gt;end&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;With that in place, we can implement some simple physics for the ball.&lt;/p&gt;
&lt;h3 id=&#34;working-with-bump-dot-lua&#34;&gt;Working with &lt;code&gt;bump.lua&lt;/code&gt;&lt;/h3&gt;
&lt;p&gt;Since we&amp;rsquo;ve loaded everything into the world we&amp;rsquo;ve created with &lt;code&gt;bump.lua&lt;/code&gt;, we can now manipulate things in it.
We&amp;rsquo;ll be mainly manipulating balls, so let&amp;rsquo;s add some methods to the &lt;code&gt;balls&lt;/code&gt; table:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-lua&#34; data-lang=&#34;lua&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;setmetatable(
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    balls,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        __index = {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            update = &lt;span style=&#34;font-weight:bold&#34;&gt;function&lt;/span&gt;(self)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                &lt;span style=&#34;font-weight:bold&#34;&gt;for&lt;/span&gt; ball &lt;span style=&#34;font-weight:bold&#34;&gt;in&lt;/span&gt; pairs(self) &lt;span style=&#34;font-weight:bold&#34;&gt;do&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                    ball.yvel = lerp(ball.yvel, 5, 0.1)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                    ball.xvel = lerp(ball.xvel, 0, 0.1)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                    ball.x, ball.y, cols, len = world:move(ball, ball.x + ball.xvel, ball.y + ball.yvel)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                    &lt;span style=&#34;font-weight:bold&#34;&gt;for&lt;/span&gt; i = 1, len &lt;span style=&#34;font-weight:bold&#34;&gt;do&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                        &lt;span style=&#34;font-weight:bold;font-style:italic&#34;&gt;local&lt;/span&gt; col = cols[i]
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                        &lt;span style=&#34;font-weight:bold&#34;&gt;if&lt;/span&gt; col.normal.y == -1 &lt;span style=&#34;font-weight:bold&#34;&gt;then&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                            ball.yvel = -(ball.yvel * 0.9)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                        &lt;span style=&#34;font-weight:bold&#34;&gt;end&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                        &lt;span style=&#34;font-weight:bold&#34;&gt;if&lt;/span&gt; col.normal.x ~= 0 &lt;span style=&#34;font-weight:bold&#34;&gt;then&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                            ball.xvel = -(ball.xvel * 0.9)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            &lt;span style=&#34;font-weight:bold&#34;&gt;end&lt;/span&gt; &lt;span style=&#34;font-weight:bold&#34;&gt;end&lt;/span&gt; &lt;span style=&#34;font-weight:bold&#34;&gt;end&lt;/span&gt; &lt;span style=&#34;font-weight:bold&#34;&gt;end&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            draw = &lt;span style=&#34;font-weight:bold&#34;&gt;function&lt;/span&gt;(self)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                &lt;span style=&#34;font-weight:bold&#34;&gt;for&lt;/span&gt; ball &lt;span style=&#34;font-weight:bold&#34;&gt;in&lt;/span&gt; pairs(self) &lt;span style=&#34;font-weight:bold&#34;&gt;do&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                    spr(1, ball.x, ball.y, 0)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            &lt;span style=&#34;font-weight:bold&#34;&gt;end&lt;/span&gt; &lt;span style=&#34;font-weight:bold&#34;&gt;end&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;I&amp;rsquo;ve added two methods: &lt;code&gt;draw&lt;/code&gt; and &lt;code&gt;update&lt;/code&gt;.
It&amp;rsquo;s not necessary to separate these, you could do everything in one call, but it&amp;rsquo;s a bit easier to reason about them this way.
Yeah, I know, looping over all of the balls two times just to update each, and then draw each is bad design, since we could do it in one loop, but that&amp;rsquo;s not the main focus here.
But it&amp;rsquo;s great that you&amp;rsquo;ve pointed this out!
Ten points to the TIC house!&lt;/p&gt;
&lt;p&gt;Anyway, we&amp;rsquo;re looping over all of the balls in the table, which we don&amp;rsquo;t have that many yet, only one, and updating each one.
The important part here is that we compute the ball&amp;rsquo;s X and Y velocity, and then use them to move the ball in the world, by calling &lt;code&gt;world:move()&lt;/code&gt;.
I&amp;rsquo;m using linear interpolation, defined as follows:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-lua&#34; data-lang=&#34;lua&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;font-weight:bold;font-style:italic&#34;&gt;local&lt;/span&gt; &lt;span style=&#34;font-weight:bold&#34;&gt;function&lt;/span&gt; &lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;lerp&lt;/span&gt;(a, b, t)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;font-weight:bold&#34;&gt;return&lt;/span&gt; ((1 - t) * a) + (t * b)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;font-weight:bold&#34;&gt;end&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;We don&amp;rsquo;t care that much about delta-time here, as TIC runs at a consistent 60 frames per second even when you push it to the limits.
You can, of course, slow it down with bad code, and we already have two loops instead of one, which is concerning, but we&amp;rsquo;ll be fine, don&amp;rsquo;t worry.
Lua is quite fast.&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-lua&#34; data-lang=&#34;lua&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;ball.x, ball.y, cols, len = world:move(ball, ball.x + ball.xvel, ball.y + ball.yvel)
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;So by calling &lt;code&gt;world:move()&lt;/code&gt; with the ball object, we receive new X and Y positions of that ball after it was moved with respect to collisions.
We&amp;rsquo;re not going to build a sophisticated physics engine here, but we will need to handle some collisions.
&lt;code&gt;bump.lua&lt;/code&gt; actually has different &lt;a href=&#34;https://github.com/kikito/bump.lua?tab=readme-ov-file#collision-resolution&#34; target=&#34;_blank&#34;&gt;collision types&lt;/a&gt;, like &lt;code&gt;bounce&lt;/code&gt;, that we could use here, but for our purposes the default &lt;code&gt;slide&lt;/code&gt; is better.&lt;/p&gt;
&lt;p&gt;In addition to the new X and Y positions, this function returns a list of collisions that happened during the move.
We can loop over those and update our ball&amp;rsquo;s properties, such as its velocity.
We do so in the following loop&lt;sup id=&#34;fnref:3&#34;&gt;&lt;a href=&#34;#fn:3&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;3&lt;/a&gt;&lt;/sup&gt;:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-lua&#34; data-lang=&#34;lua&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;font-weight:bold&#34;&gt;for&lt;/span&gt; i = 1, len &lt;span style=&#34;font-weight:bold&#34;&gt;do&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;font-weight:bold;font-style:italic&#34;&gt;local&lt;/span&gt; col = cols[i]
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;font-weight:bold&#34;&gt;if&lt;/span&gt; col.normal.y == -1 &lt;span style=&#34;font-weight:bold&#34;&gt;then&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        ball.yvel = -(ball.yvel * 0.9)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;font-weight:bold&#34;&gt;end&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;font-weight:bold&#34;&gt;if&lt;/span&gt; col.normal.x ~= 0 &lt;span style=&#34;font-weight:bold&#34;&gt;then&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        ball.xvel = -(ball.xvel * 0.9)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;font-weight:bold&#34;&gt;end&lt;/span&gt; &lt;span style=&#34;font-weight:bold&#34;&gt;end&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;We&amp;rsquo;re slowly decreasing the ball&amp;rsquo;s velocity, also switching directions on contact.
That&amp;rsquo;s pretty much what the &lt;code&gt;bounce&lt;/code&gt; collision would do for us.&lt;/p&gt;
&lt;p&gt;For the &lt;code&gt;draw&lt;/code&gt; method, we simply draw the ball sprite using another TIC&amp;rsquo;s builtin function &lt;code&gt;spr&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Having these implemented, we can add them to the &lt;code&gt;TIC&lt;/code&gt; function, like so:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-lua&#34; data-lang=&#34;lua&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;font-weight:bold&#34;&gt;function&lt;/span&gt; &lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;TIC&lt;/span&gt;()
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    cls(0)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    map(0, 0, 31, 18, 0, 0, 0, 1)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    balls:update()
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    balls:draw()
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;font-weight:bold&#34;&gt;end&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Now, if we run our game, we&amp;rsquo;ll see our ball bouncing:&lt;/p&gt;
&lt;p&gt;&lt;video autoplay loop muted&gt;&lt;source src=&#34;https://andreyor.st/2026-01-09-slopes-in-aabb-collision-systems/ball-bounce.mp4&#34; type=&#34;video/mp4&#34;&gt;
Your browser does not support the &lt;video&gt; tag&lt;/video&gt;&lt;/p&gt;
&lt;p&gt;Uh oh, why are there two balls, and one floating over the ground?
Did we forget to add it to the &lt;code&gt;balls&lt;/code&gt; table, so it gets updated?
If we did, why is it even drawn - we loop over balls in both functions.&lt;/p&gt;
&lt;p&gt;If you look closer, you&amp;rsquo;ll see the problem.
We&amp;rsquo;ve loaded the map to the world, and we&amp;rsquo;re drawing the ball using the &lt;code&gt;spr&lt;/code&gt; function.
But we&amp;rsquo;re still drawing the original map in the &lt;code&gt;TIC&lt;/code&gt; function by calling &lt;code&gt;map&lt;/code&gt;!
And our map still has the ball.&lt;/p&gt;
&lt;p&gt;We can go two ways here.
One would be to use the &lt;code&gt;mset&lt;/code&gt; function and replace the ball tile with nothing in our map when we&amp;rsquo;re loading it.
Or, we can hide the ball at runtime using &lt;code&gt;map&lt;/code&gt; function&amp;rsquo;s special remapping feature.
Let&amp;rsquo;s do that instead, as it is a bit cleaner:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-lua&#34; data-lang=&#34;lua&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;font-weight:bold;font-style:italic&#34;&gt;local&lt;/span&gt; &lt;span style=&#34;font-weight:bold&#34;&gt;function&lt;/span&gt; &lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;remap&lt;/span&gt;(id)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;font-weight:bold&#34;&gt;if&lt;/span&gt; is_ball(id) &lt;span style=&#34;font-weight:bold&#34;&gt;then&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;font-weight:bold&#34;&gt;return&lt;/span&gt; 0
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;font-weight:bold&#34;&gt;end&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;font-weight:bold&#34;&gt;return&lt;/span&gt; id
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;font-weight:bold&#34;&gt;end&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Now, we update our &lt;code&gt;TIC&lt;/code&gt; function to use this &lt;code&gt;remap&lt;/code&gt; function in our &lt;code&gt;map&lt;/code&gt; call:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-lua&#34; data-lang=&#34;lua&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;font-weight:bold&#34;&gt;function&lt;/span&gt; &lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;TIC&lt;/span&gt;()
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    cls(0)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    map(0, 0, 31, 18, 0, 0, 0, 1, remap)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    balls:update()
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    balls:draw()
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;font-weight:bold&#34;&gt;end&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;And, presto!&lt;/p&gt;
&lt;p&gt;&lt;video autoplay loop muted&gt;&lt;source src=&#34;https://andreyor.st/2026-01-09-slopes-in-aabb-collision-systems/basic-physics.mp4&#34; type=&#34;video/mp4&#34;&gt;
Your browser does not support the &lt;video&gt; tag&lt;/video&gt;&lt;/p&gt;
&lt;h3 id=&#34;handling-mouse-events&#34;&gt;Handling mouse events&lt;/h3&gt;
&lt;p&gt;Our introduction to TIC isn&amp;rsquo;t over yet, because I always wanted to try using the mouse integration.
Our ball is currently pretty stiff, so we&amp;rsquo;ll tweak its physics along the way.
Having mouse support will make it easier to test things out, as doing so by updating the code is a bit cumbersome.
And I don&amp;rsquo;t know how to make sensible controls for a ball with arrow keys - it&amp;rsquo;s just a bouncing ball, what do you expect it to do, roll by itself when you press a magic key??
Let&amp;rsquo;s drag the balls instead.&lt;/p&gt;
&lt;p&gt;We&amp;rsquo;ll start by creating a small storage variable that we&amp;rsquo;ll assign to the currently grabbed ball:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-lua&#34; data-lang=&#34;lua&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;font-weight:bold;font-style:italic&#34;&gt;local&lt;/span&gt; grabbed = &lt;span style=&#34;font-weight:bold&#34;&gt;nil&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;It&amp;rsquo;s uninitialized by default, we&amp;rsquo;ll set it later in the &lt;code&gt;update&lt;/code&gt; method:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-lua&#34; data-lang=&#34;lua&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;font-weight:bold;font-style:italic&#34;&gt;local&lt;/span&gt; mx, my, is_pressed = mouse()
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;font-weight:bold&#34;&gt;if&lt;/span&gt; is_pressed &lt;span style=&#34;font-weight:bold&#34;&gt;and&lt;/span&gt; &lt;span style=&#34;font-weight:bold&#34;&gt;not&lt;/span&gt; grabbed &lt;span style=&#34;font-weight:bold&#34;&gt;then&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;font-weight:bold;font-style:italic&#34;&gt;local&lt;/span&gt; items = world:queryPoint(mx, my, &lt;span style=&#34;font-weight:bold&#34;&gt;function&lt;/span&gt;(obj) &lt;span style=&#34;font-weight:bold&#34;&gt;return&lt;/span&gt; obj.is_ball &lt;span style=&#34;font-weight:bold&#34;&gt;end&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;font-weight:bold&#34;&gt;if&lt;/span&gt; next(items) &lt;span style=&#34;font-weight:bold&#34;&gt;then&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        grabbed = items[1]
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;font-weight:bold&#34;&gt;end&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;font-weight:bold&#34;&gt;elseif&lt;/span&gt; &lt;span style=&#34;font-weight:bold&#34;&gt;not&lt;/span&gt; is_pressed &lt;span style=&#34;font-weight:bold&#34;&gt;then&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;font-weight:bold&#34;&gt;if&lt;/span&gt; grabbed &lt;span style=&#34;font-weight:bold&#34;&gt;then&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        grabbed.xvel = (grabbed.x - grabbed.prev_x)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        grabbed.yvel = (grabbed.y - grabbed.prev_y)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        grabbed.prev_x, grabbed.prev_y = &lt;span style=&#34;font-weight:bold&#34;&gt;nil&lt;/span&gt;, &lt;span style=&#34;font-weight:bold&#34;&gt;nil&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;font-weight:bold&#34;&gt;end&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    grabbed = &lt;span style=&#34;font-weight:bold&#34;&gt;nil&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;font-weight:bold&#34;&gt;end&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;We add this before the main loop over all balls in the &lt;code&gt;update&lt;/code&gt; function.&lt;/p&gt;
&lt;p&gt;The &lt;code&gt;mouse&lt;/code&gt; function in TIC returns a set of values, X and Y coordinates, and states of each mouse button.
We&amp;rsquo;re only interested in the left mouse button, so that&amp;rsquo;s what we store.&lt;/p&gt;
&lt;p&gt;Then, if the button is pressed, we use a method from the &lt;a href=&#34;https://github.com/kikito/bump.lua?tab=readme-ov-file#querying-with-a-point&#34; target=&#34;_blank&#34;&gt;Intermediate API&lt;/a&gt; section of &lt;code&gt;bump.lua&lt;/code&gt;.
It returns a list of items, and since we don&amp;rsquo;t have anything that overlaps, we can safely take the first item in the list.
We&amp;rsquo;re also using a similar feature to the &lt;code&gt;map&lt;/code&gt; function&amp;rsquo;s remap here - we&amp;rsquo;re providing a filtering function that will make sure that we can only grab ball objects, not other tiles.&lt;/p&gt;
&lt;p&gt;When the button is released, its state will change, and we need to release the ball.
If there was a ball grabbed, we set its velocity to conserve the momentum from moving it by the mouse, which we&amp;rsquo;ll handle later.
Then we unset the &lt;code&gt;grabbed&lt;/code&gt; storage variable.&lt;/p&gt;
&lt;p&gt;Next, in the &lt;code&gt;for&lt;/code&gt; loop responsible for updating the balls, we check if the current ball is the one we&amp;rsquo;ve grabbed, and we override its movement:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-lua&#34; data-lang=&#34;lua&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;font-weight:bold&#34;&gt;for&lt;/span&gt; ball &lt;span style=&#34;font-weight:bold&#34;&gt;in&lt;/span&gt; pairs(self) &lt;span style=&#34;font-weight:bold&#34;&gt;do&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;font-weight:bold&#34;&gt;if&lt;/span&gt; ball == grabbed &lt;span style=&#34;font-weight:bold&#34;&gt;then&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;font-weight:bold;font-style:italic&#34;&gt;local&lt;/span&gt; x, y = lerp(grabbed.x, mx-4, 0.3), lerp(grabbed.y, my-4, 0.3)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        grabbed.prev_x, grabbed.prev_y = grabbed.x, grabbed.y
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        ball.x, ball.y = world:move(ball, x, y)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;font-weight:bold&#34;&gt;else&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#888;font-style:italic&#34;&gt;-- no changes to the old logic&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;font-weight:bold&#34;&gt;end&lt;/span&gt; &lt;span style=&#34;font-weight:bold&#34;&gt;end&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;So, as you can see, we&amp;rsquo;re moving the ball to the mouse pointer coordinates, still with respect to collisions.
We also store additional information in the &lt;code&gt;grabbed&lt;/code&gt; object, mainly the previous X and Y positions that we use in that momentum conservation code a bit earlier.
And with that, we can drag the ball around:&lt;/p&gt;
&lt;p&gt;&lt;video autoplay loop muted&gt;&lt;source src=&#34;https://andreyor.st/2026-01-09-slopes-in-aabb-collision-systems/mouse-drag.mp4&#34; type=&#34;video/mp4&#34;&gt;
Your browser does not support the &lt;video&gt; tag&lt;/video&gt;&lt;/p&gt;
&lt;p&gt;This is as basic as it gets - you&amp;rsquo;ll notice that if we add more balls, we can&amp;rsquo;t move other balls with the one we&amp;rsquo;re dragging.
You can, of course, make a more sophisticated physics simulation - handle collisions better, so it would move other balls as well, transfer the momentum to other objects, make balls roll off each other, as right now they&amp;rsquo;re basically squares, and so on.&lt;/p&gt;
&lt;p&gt;So you can do something like that if you try experimenting with collision resolution a bit more:&lt;/p&gt;
&lt;p&gt;&lt;video autoplay loop muted&gt;&lt;source src=&#34;https://andreyor.st/2026-01-09-slopes-in-aabb-collision-systems/ball-interactions.mp4&#34; type=&#34;video/mp4&#34;&gt;
Your browser does not support the &lt;video&gt; tag&lt;/video&gt;&lt;/p&gt;
&lt;p&gt;Now, we&amp;rsquo;re pretty much set for working towards the main topic of this post - slopes.&lt;/p&gt;
&lt;h2 id=&#34;implementing-slopes-with-aabb-collisions&#34;&gt;Implementing slopes with AABB collisions&lt;/h2&gt;
&lt;p&gt;Phew, FINALLY, all that just to tell you that to handle slopes you just need to do&amp;hellip;&lt;/p&gt;
&lt;figure class=&#34;invertable&#34;&gt;&lt;img src=&#34;https://andreyor.st/2026-01-09-slopes-in-aabb-collision-systems/aabb-slope-handling.png&#34;
         alt=&#34;Figure 3: &amp;amp;hellip;is this.&#34;/&gt;&lt;figcaption&gt;
            &lt;p&gt;&lt;span class=&#34;figure-number&#34;&gt;Figure 3: &lt;/span&gt;&amp;hellip;is this.&lt;/p&gt;
        &lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p&gt;OK, this is anticlimactic, so let&amp;rsquo;s implement this.
But first, what is a slope in our case?&lt;/p&gt;
&lt;p&gt;Because we can only have axis-aligned rectangles, we need to create a special kind of object in our world that will still be a rectangle, but handle collisions differently.
Since our game is tile-based, we don&amp;rsquo;t have a lot of meaningful angles to support.
The most common slope in games is a segment with dimensions of &lt;code&gt;16x8&lt;/code&gt;.
It spans exactly two tiles and takes you one tile height up.
Let&amp;rsquo;s draw it:&lt;/p&gt;
&lt;figure&gt;&lt;img src=&#34;https://andreyor.st/2026-01-09-slopes-in-aabb-collision-systems/slope-sprites.png&#34;/&gt;
&lt;/figure&gt;

&lt;p&gt;You can see that I&amp;rsquo;ve set bit 2 to the slope tile here, so we can correctly handle it while loading our map to the world.
If we add these to the world right now, they won&amp;rsquo;t work, obviously, since these are regular rectangles:&lt;/p&gt;
&lt;p&gt;&lt;video autoplay loop muted&gt;&lt;source src=&#34;https://andreyor.st/2026-01-09-slopes-in-aabb-collision-systems/slopes-dont-work-yet.mp4&#34; type=&#34;video/mp4&#34;&gt;
Your browser does not support the &lt;video&gt; tag&lt;/video&gt;&lt;/p&gt;
&lt;p&gt;So let&amp;rsquo;s change that:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-lua&#34; data-lang=&#34;lua&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;font-weight:bold;font-style:italic&#34;&gt;local&lt;/span&gt; &lt;span style=&#34;font-weight:bold&#34;&gt;function&lt;/span&gt; &lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;is_up_slope&lt;/span&gt;(id)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;font-weight:bold&#34;&gt;return&lt;/span&gt; fget(id, 2)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;font-weight:bold&#34;&gt;end&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;font-weight:bold;font-style:italic&#34;&gt;local&lt;/span&gt; &lt;span style=&#34;font-weight:bold&#34;&gt;function&lt;/span&gt; &lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;is_down_slope&lt;/span&gt;(id)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;font-weight:bold&#34;&gt;return&lt;/span&gt; fget(id, 3)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;font-weight:bold&#34;&gt;end&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;We&amp;rsquo;ll start by defining two more predicates, and then use them in our &lt;code&gt;is_obj&lt;/code&gt; function:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-lua&#34; data-lang=&#34;lua&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;font-weight:bold;font-style:italic&#34;&gt;local&lt;/span&gt; &lt;span style=&#34;font-weight:bold&#34;&gt;function&lt;/span&gt; &lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;is_obj&lt;/span&gt;(x, y)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;font-weight:bold;font-style:italic&#34;&gt;local&lt;/span&gt; id = id_at(x, y)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;font-weight:bold&#34;&gt;if&lt;/span&gt; is_solid(id) &lt;span style=&#34;font-weight:bold&#34;&gt;then&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;font-weight:bold&#34;&gt;return&lt;/span&gt; {x = x, y = y, h = 8, w = 8}
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;font-weight:bold&#34;&gt;elseif&lt;/span&gt; is_up_slope(id) &lt;span style=&#34;font-weight:bold&#34;&gt;then&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;font-weight:bold&#34;&gt;return&lt;/span&gt; {x = x, y = y, h = 8, w = 16, slope = slope, dir = 1}
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;font-weight:bold&#34;&gt;elseif&lt;/span&gt; is_down_slope(id) &lt;span style=&#34;font-weight:bold&#34;&gt;then&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;font-weight:bold&#34;&gt;return&lt;/span&gt; {x = x, y = y, h = 8, w = 16, slope = slope, dir = -1}
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;font-weight:bold&#34;&gt;elseif&lt;/span&gt; is_ball(id) &lt;span style=&#34;font-weight:bold&#34;&gt;then&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;font-weight:bold;font-style:italic&#34;&gt;local&lt;/span&gt; ball = {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            x = x,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            y = y,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            h = 8,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            w = 8,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            xvel = 0,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            yvel = 0,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            is_ball = &lt;span style=&#34;font-weight:bold&#34;&gt;true&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        balls[ball] = &lt;span style=&#34;font-weight:bold&#34;&gt;true&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;font-weight:bold&#34;&gt;return&lt;/span&gt; ball
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;font-weight:bold&#34;&gt;end&lt;/span&gt; &lt;span style=&#34;font-weight:bold&#34;&gt;end&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;As you can see, these are regular rectangles, except in our world, the &lt;code&gt;8x8&lt;/code&gt; tile creates a &lt;code&gt;16x8&lt;/code&gt; rectangle.
The other half of the slope is just decoration.&lt;/p&gt;
&lt;p&gt;We also have the &lt;code&gt;slope&lt;/code&gt; and &lt;code&gt;dir&lt;/code&gt; fields here.
The &lt;code&gt;slope&lt;/code&gt; is a function responsible for handling collisions that we&amp;rsquo;ll implement shortly.
The &lt;code&gt;dir&lt;/code&gt; field determines if this is an up-slope, raising from left to right, or a down-slope, lowering from left to right (or raising from right to left).
We need to know the direction, otherwise, we won&amp;rsquo;t be able to calculate normals and properly map coordinates to new values.&lt;/p&gt;
&lt;h3 id=&#34;calculating-slope-collision-coordinates&#34;&gt;Calculating slope collision coordinates&lt;/h3&gt;
&lt;p&gt;Here&amp;rsquo;s the &lt;code&gt;slope&lt;/code&gt; function:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-lua&#34; data-lang=&#34;lua&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;font-weight:bold;font-style:italic&#34;&gt;local&lt;/span&gt; &lt;span style=&#34;font-weight:bold&#34;&gt;function&lt;/span&gt; &lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;slope&lt;/span&gt;(obj)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;font-weight:bold&#34;&gt;if&lt;/span&gt; obj.yvel &amp;gt;= 0 &lt;span style=&#34;font-weight:bold&#34;&gt;then&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;font-weight:bold;font-style:italic&#34;&gt;local&lt;/span&gt; _, _, cols, len = world:check(obj, obj.x, obj.y + obj.yvel, slope_filter)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;font-weight:bold&#34;&gt;for&lt;/span&gt; i=1, len &lt;span style=&#34;font-weight:bold&#34;&gt;do&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            &lt;span style=&#34;font-weight:bold;font-style:italic&#34;&gt;local&lt;/span&gt; col = cols[i]
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            &lt;span style=&#34;font-weight:bold&#34;&gt;if&lt;/span&gt; col.normal.y ~= 1 &lt;span style=&#34;font-weight:bold&#34;&gt;then&lt;/span&gt; &lt;span style=&#34;color:#888;font-style:italic&#34;&gt;-- -1 or 0 will do&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                &lt;span style=&#34;font-weight:bold;font-style:italic&#34;&gt;local&lt;/span&gt; slope_floor = col.other.y - col.other.h
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                &lt;span style=&#34;font-weight:bold;font-style:italic&#34;&gt;local&lt;/span&gt; delta = clamp(0, (obj.x + obj.w / 2) - col.other.x, col.other.w) / col.other.w
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                &lt;span style=&#34;font-weight:bold;font-style:italic&#34;&gt;local&lt;/span&gt; y_off = &lt;span style=&#34;font-weight:bold&#34;&gt;nil&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                &lt;span style=&#34;font-weight:bold&#34;&gt;if&lt;/span&gt; col.other.dir &amp;gt; 0 &lt;span style=&#34;font-weight:bold&#34;&gt;then&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                    y_off = lerp(col.other.h, 0, delta)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                &lt;span style=&#34;font-weight:bold&#34;&gt;else&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                    y_off = lerp(0, col.other.h, delta)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                &lt;span style=&#34;font-weight:bold&#34;&gt;end&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                &lt;span style=&#34;font-weight:bold;font-style:italic&#34;&gt;local&lt;/span&gt; newy = slope_floor + y_off
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                &lt;span style=&#34;font-weight:bold&#34;&gt;return&lt;/span&gt; math.floor(newy), slope_normal(col.other)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;font-weight:bold&#34;&gt;end&lt;/span&gt; &lt;span style=&#34;font-weight:bold&#34;&gt;end&lt;/span&gt; &lt;span style=&#34;font-weight:bold&#34;&gt;end&lt;/span&gt; &lt;span style=&#34;font-weight:bold&#34;&gt;end&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Now, that&amp;rsquo;s a lot to take in.
Let&amp;rsquo;s go through step by step.&lt;/p&gt;
&lt;p&gt;First, we check if the object&amp;rsquo;s velocity is going at least downwards.
We don&amp;rsquo;t want to trigger slope collisions when objects move away from the slope, so that&amp;rsquo;s pretty simple.&lt;/p&gt;
&lt;p&gt;Next, as usual, we need to handle collisions.
However, there&amp;rsquo;s a catch - since our slopes are rectangles in disguise, we can&amp;rsquo;t just use the normal &lt;code&gt;world:move()&lt;/code&gt; and be done.
Instead, we need to kinda implement the &lt;code&gt;world:move()&lt;/code&gt; in terms of &lt;code&gt;world:check()&lt;/code&gt; and &lt;code&gt;world:update()&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;The &lt;code&gt;check&lt;/code&gt; method does the same stuff as &lt;code&gt;move&lt;/code&gt; except it doesn&amp;rsquo;t move anything.
It pretends that the object has moved, and returns a list of collisions and coordinates where it would end up if it actually moved.
That&amp;rsquo;s perfect for our needs.
The &lt;code&gt;update&lt;/code&gt; method can move the object around without checking for any collisions, and that&amp;rsquo;s the other piece of the puzzle.&lt;/p&gt;
&lt;p&gt;But right now, the ball collides with the slope rectangle, and it can&amp;rsquo;t go further, what can we do?
We use filtering, of course.&lt;/p&gt;
&lt;p&gt;The &lt;code&gt;world:check()&lt;/code&gt; also accepts the collision filter, as &lt;code&gt;world:move()&lt;/code&gt; does.
We&amp;rsquo;ll need to add two new filter functions, one for checking slopes specifically, and the other to allow balls to move through slope rectangles freely:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-lua&#34; data-lang=&#34;lua&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;font-weight:bold;font-style:italic&#34;&gt;local&lt;/span&gt; &lt;span style=&#34;font-weight:bold&#34;&gt;function&lt;/span&gt; &lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;slope_filter&lt;/span&gt;(_, other)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;font-weight:bold&#34;&gt;if&lt;/span&gt; other.slope &lt;span style=&#34;font-weight:bold&#34;&gt;then&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;font-weight:bold&#34;&gt;return&lt;/span&gt; &lt;span style=&#34;color:#666;font-style:italic&#34;&gt;&amp;#34;cross&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;font-weight:bold&#34;&gt;end&lt;/span&gt; &lt;span style=&#34;font-weight:bold&#34;&gt;end&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;font-weight:bold;font-style:italic&#34;&gt;local&lt;/span&gt; &lt;span style=&#34;font-weight:bold&#34;&gt;function&lt;/span&gt; &lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;ball_filter&lt;/span&gt;(_, other)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;font-weight:bold&#34;&gt;if&lt;/span&gt; other.slope &lt;span style=&#34;font-weight:bold&#34;&gt;then&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;font-weight:bold&#34;&gt;return&lt;/span&gt; &lt;span style=&#34;color:#666;font-style:italic&#34;&gt;&amp;#34;cross&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;font-weight:bold&#34;&gt;else&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;font-weight:bold&#34;&gt;return&lt;/span&gt; &lt;span style=&#34;color:#666;font-style:italic&#34;&gt;&amp;#34;slide&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;font-weight:bold&#34;&gt;end&lt;/span&gt; &lt;span style=&#34;font-weight:bold&#34;&gt;end&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;With that out of the way, we can go into collision checking.&lt;/p&gt;
&lt;p&gt;As usual, we iterate over a list of collisions.
We then check if the collision&amp;rsquo;s Y component of the normal vector points at least up.
If so, we have our collision, but we are just getting started.&lt;/p&gt;
&lt;p&gt;Next, we calculate the slope floor Y position.
This is the lowest coordinate our collision could possibly have:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-lua&#34; data-lang=&#34;lua&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;font-weight:bold;font-style:italic&#34;&gt;local&lt;/span&gt; slope_floor = col.other.y - col.other.h
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Next, we calculate the delta:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-lua&#34; data-lang=&#34;lua&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;font-weight:bold;font-style:italic&#34;&gt;local&lt;/span&gt; delta = clamp(0, (obj.x + obj.w / 2) - col.other.x, col.other.w) / col.other.w
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;This is how far from the left edge of the slope rectangle the collision is going to happen.
We try to measure in the center of the colliding tile, and normalize it to be a value from &lt;code&gt;0&lt;/code&gt; to &lt;code&gt;1&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Next, we need to determine the Y coordinate on that slope:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-lua&#34; data-lang=&#34;lua&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;font-weight:bold;font-style:italic&#34;&gt;local&lt;/span&gt; y_off = &lt;span style=&#34;font-weight:bold&#34;&gt;nil&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;font-weight:bold&#34;&gt;if&lt;/span&gt; col.other.dir &amp;gt; 0 &lt;span style=&#34;font-weight:bold&#34;&gt;then&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    y_off = lerp(col.other.h, 0, delta)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;font-weight:bold&#34;&gt;else&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    y_off = lerp(0, col.other.h, delta)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;font-weight:bold&#34;&gt;end&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;We do so by checking the slope direction, and do a linear interpolation between &lt;code&gt;0&lt;/code&gt; and slope height, using our delta value.
Linear interpolation will move us in a straight diagonal line from the bottom corner of the slope to the top corner of the slope.
Direction is simply used to flip the interpolation.&lt;/p&gt;
&lt;p&gt;Finally, we&amp;rsquo;re calculating a ney Y position, and normals to return:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-lua&#34; data-lang=&#34;lua&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;font-weight:bold;font-style:italic&#34;&gt;local&lt;/span&gt; newy = slope_floor + y_off
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;font-weight:bold&#34;&gt;return&lt;/span&gt; math.floor(newy), slope_normal(col.other)
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Our Y position from &lt;code&gt;lerp&lt;/code&gt; is relative to the slope, so we add back the &lt;code&gt;slope_floor&lt;/code&gt; position to translate it back to world coordinates.&lt;/p&gt;
&lt;p&gt;And that&amp;rsquo;s pretty much just a small part of it!
Wait&amp;hellip;&lt;/p&gt;
&lt;h3 id=&#34;handling-slope-collisions&#34;&gt;Handling slope collisions&lt;/h3&gt;
&lt;p&gt;We still need to adjust the object&amp;rsquo;s position to that coordinate - why didn&amp;rsquo;t we do so in the slope function itself?
Well, because reasons&amp;hellip;&lt;/p&gt;
&lt;p&gt;I mean, I tried to do that, but it didn&amp;rsquo;t work well, there are many outside factors that we may need to consider, and it&amp;rsquo;s not the best way to move it to this function.
So let&amp;rsquo;s change our &lt;code&gt;update&lt;/code&gt; method of the &lt;code&gt;balls&lt;/code&gt; table.&lt;/p&gt;
&lt;p&gt;First, let&amp;rsquo;s handle normal ball collisions.
Mouse collisions will come later:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-lua&#34; data-lang=&#34;lua&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;font-weight:bold&#34;&gt;for&lt;/span&gt; ball &lt;span style=&#34;font-weight:bold&#34;&gt;in&lt;/span&gt; pairs(self) &lt;span style=&#34;font-weight:bold&#34;&gt;do&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;font-weight:bold&#34;&gt;if&lt;/span&gt; ball == grabbed &lt;span style=&#34;font-weight:bold&#34;&gt;then&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#888;font-style:italic&#34;&gt;--- 8&amp;lt; ---&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;font-weight:bold&#34;&gt;else&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        ball.yvel = lerp(ball.yvel, 5, 0.075)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        ball.xvel = lerp(ball.xvel, 0, 0.03)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        ball.x, ball.y, cols, len = world:move(ball, ball.x + ball.xvel, ball.y + ball.yvel, ball_filter)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;font-weight:bold&#34;&gt;for&lt;/span&gt; i = 1, len &lt;span style=&#34;font-weight:bold&#34;&gt;do&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            &lt;span style=&#34;font-weight:bold;font-style:italic&#34;&gt;local&lt;/span&gt; col = cols[i]
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            &lt;span style=&#34;font-weight:bold&#34;&gt;if&lt;/span&gt; col.other.slope &lt;span style=&#34;font-weight:bold&#34;&gt;then&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                &lt;span style=&#34;font-weight:bold;font-style:italic&#34;&gt;local&lt;/span&gt; newy, xnorm, ynorm = col.other.slope(ball)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                &lt;span style=&#34;font-weight:bold&#34;&gt;if&lt;/span&gt; newy &lt;span style=&#34;font-weight:bold&#34;&gt;and&lt;/span&gt; ball.y &amp;gt;= newy &lt;span style=&#34;font-weight:bold&#34;&gt;then&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                    world:update(ball, ball.x, newy, ball.w, ball.h)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                    ball.y = newy
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                    ball.xvel = ball.xvel + xnorm
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                    ball.yvel = (- ball.yvel - ynorm) * 0.5
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                    &lt;span style=&#34;font-weight:bold&#34;&gt;break&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                &lt;span style=&#34;font-weight:bold&#34;&gt;end&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            &lt;span style=&#34;font-weight:bold&#34;&gt;else&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                &lt;span style=&#34;font-weight:bold&#34;&gt;if&lt;/span&gt; col.normal.y ~= 0 &lt;span style=&#34;font-weight:bold&#34;&gt;then&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                    ball.yvel = -(ball.yvel * 0.9)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                    ball.xvel = ball.xvel * 0.75
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                &lt;span style=&#34;font-weight:bold&#34;&gt;end&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                &lt;span style=&#34;font-weight:bold&#34;&gt;if&lt;/span&gt; col.normal.x ~= 0 &lt;span style=&#34;font-weight:bold&#34;&gt;then&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                    ball.xvel = -(ball.xvel * 0.9)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;font-weight:bold&#34;&gt;end&lt;/span&gt; &lt;span style=&#34;font-weight:bold&#34;&gt;end&lt;/span&gt; &lt;span style=&#34;font-weight:bold&#34;&gt;end&lt;/span&gt; &lt;span style=&#34;font-weight:bold&#34;&gt;end&lt;/span&gt; &lt;span style=&#34;font-weight:bold&#34;&gt;end&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;div class=&#34;src-block-caption&#34;&gt;
  &lt;span class=&#34;src-block-number&#34;&gt;Code Snippet 1:&lt;/span&gt;
  Imagine if I didn&#39;t stack the &lt;code&gt;end&lt;/code&gt; keywords on the same line? This post would be even longer! It&#39;s already a bit too long&amp;hellip;
&lt;/div&gt;
&lt;p&gt;Main differences from before: we added &lt;code&gt;ball_filter&lt;/code&gt; to the &lt;code&gt;world:move()&lt;/code&gt; call, and split collision handling logic into two separate branches.
The first branch checks if the collision happens with a slope and handles it.
The second branch is the same as before.&lt;/p&gt;
&lt;p&gt;The collision handling is pretty simple, really - we check if &lt;code&gt;slope&lt;/code&gt; actually returned a new coordinate, and if the ball&amp;rsquo;s current Y position is lower than that, we need to handle the collision.
The &lt;code&gt;ball_filter&lt;/code&gt; sets slope collisions to &lt;code&gt;cross&lt;/code&gt;, and it allows balls to move through slope blocks as if they&amp;rsquo;re not there, except we still get collision information.
So if the ball&amp;rsquo;s Y coordinate is less than the slope collision coordinate, it means that we need to push the ball out of the slope.
We do it straight up, without concerns about the normal vector to the slope, but it is possible to do so (I&amp;rsquo;m just lazy).&lt;/p&gt;
&lt;p&gt;Calling &lt;code&gt;world:update()&lt;/code&gt; sets the ball to a new position, and we also update its fields to set the new coordinate, and update velocities.
We also break from this loop, as we don&amp;rsquo;t really want to handle any other collision, since it could get messy.&lt;/p&gt;
&lt;p&gt;Now, let&amp;rsquo;s do the same for mouse movement:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-lua&#34; data-lang=&#34;lua&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;font-weight:bold&#34;&gt;for&lt;/span&gt; ball &lt;span style=&#34;font-weight:bold&#34;&gt;in&lt;/span&gt; pairs(self) &lt;span style=&#34;font-weight:bold&#34;&gt;do&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;font-weight:bold&#34;&gt;if&lt;/span&gt; ball == grabbed &lt;span style=&#34;font-weight:bold&#34;&gt;then&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;font-weight:bold;font-style:italic&#34;&gt;local&lt;/span&gt; x, y = lerp(ball.x, mx-4, 0.3), lerp(ball.y, my-4, 0.3)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        ball.prev_x, ball.prev_y = ball.x, ball.y
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        ball.x, ball.y, cols, len = world:move(ball, x, y, ball_filter)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;font-weight:bold&#34;&gt;for&lt;/span&gt; i = 1, len &lt;span style=&#34;font-weight:bold&#34;&gt;do&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            &lt;span style=&#34;font-weight:bold;font-style:italic&#34;&gt;local&lt;/span&gt; col = cols[i]
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            &lt;span style=&#34;font-weight:bold&#34;&gt;if&lt;/span&gt; col.other.slope &lt;span style=&#34;font-weight:bold&#34;&gt;then&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                &lt;span style=&#34;font-weight:bold;font-style:italic&#34;&gt;local&lt;/span&gt; yvel = ball.yvel
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                ball.yvel = 1 &lt;span style=&#34;color:#888;font-style:italic&#34;&gt;-- needed for the slope check to happen&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                &lt;span style=&#34;font-weight:bold;font-style:italic&#34;&gt;local&lt;/span&gt; newy = col.other.slope(ball)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                ball.yvel = yvel &lt;span style=&#34;color:#888;font-style:italic&#34;&gt;-- restore the original velocity&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                &lt;span style=&#34;font-weight:bold&#34;&gt;if&lt;/span&gt; newy &lt;span style=&#34;font-weight:bold&#34;&gt;and&lt;/span&gt; ball.y &amp;gt;= newy &lt;span style=&#34;font-weight:bold&#34;&gt;then&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                    world:update(ball, ball.x, newy, ball.w, ball.h)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                    ball.y = newy
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;font-weight:bold&#34;&gt;end&lt;/span&gt; &lt;span style=&#34;font-weight:bold&#34;&gt;end&lt;/span&gt; &lt;span style=&#34;font-weight:bold&#34;&gt;end&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;font-weight:bold&#34;&gt;else&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#888;font-style:italic&#34;&gt;--- 8&amp;lt; ---&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;font-weight:bold&#34;&gt;end&lt;/span&gt; &lt;span style=&#34;font-weight:bold&#34;&gt;end&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;It&amp;rsquo;s pretty similar to the one above, except we need to do a small trick.&lt;/p&gt;
&lt;p&gt;Notice how we set the ball&amp;rsquo;s velocity to &lt;code&gt;1&lt;/code&gt; right before the check?
Since the ball is moved by the mouse, its velocity is not really updated.
Don&amp;rsquo;t ask me how long I was debugging why the collision doesn&amp;rsquo;t happen&amp;hellip;&lt;/p&gt;
&lt;p&gt;And that&amp;rsquo;s pretty much it!&lt;/p&gt;
&lt;p&gt;&lt;video autoplay loop muted&gt;&lt;source src=&#34;https://andreyor.st/2026-01-09-slopes-in-aabb-collision-systems/slopes-work.mp4&#34; type=&#34;video/mp4&#34;&gt;
Your browser does not support the &lt;video&gt; tag&lt;/video&gt;&lt;/p&gt;
&lt;p&gt;We can also add a different kind of slope to show that this approach is generic enough:&lt;/p&gt;
&lt;p&gt;&lt;video autoplay loop muted&gt;&lt;source src=&#34;https://andreyor.st/2026-01-09-slopes-in-aabb-collision-systems/more-slopes.mp4&#34; type=&#34;video/mp4&#34;&gt;
Your browser does not support the &lt;video&gt; tag&lt;/video&gt;&lt;/p&gt;
&lt;h2 id=&#34;conclusion&#34;&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;This is still, by no means, a proper physics simulation of slopes.
Balls bounce off slopes in the wrong direction at certain angles, normals are ignored when handling collisions, but hey, this might not be a problem in your game at all!&lt;/p&gt;
&lt;p&gt;For instance, in my original case, the player is the main object I need to be able to interact with slopes, and I have a pretty simplistic game-oriented physics.
So I don&amp;rsquo;t need the player to jump in a weird way when it&amp;rsquo;s on a slope - jumps should remain predictable.
There are no projectiles that bounce off walls either, and thus, this implementation works fine.&lt;/p&gt;
&lt;p&gt;You can, of course, use these principles to implement a more sophisticated engine.
And there&amp;rsquo;s still a lot of room for improvement, too.&lt;/p&gt;
&lt;p&gt;For instance, the way we define slopes is quite limited.
Sure, we can adjust the slope angle, but because we&amp;rsquo;re building large slopes from small building blocks, there&amp;rsquo;s a wonky collision at these blocks&amp;rsquo; corners.
Instead, we could define two points and programmatically generate map tiles along the slope angle up to its end, using predefined sprites.
And for non-sprite-based games, it doesn&amp;rsquo;t make sense to limit slopes to certain angles - you can define arbitrary-sized rectangles, making slopes as steep as needed.&lt;/p&gt;
&lt;p&gt;Alternatively, you can refactor this code and define slopes as mathematical functions.
Thus, you&amp;rsquo;ll be able to create splines and have curved slopes.
Normal vector calculation will be a bit tougher, though.&lt;/p&gt;
&lt;p&gt;And actually, if you happen to know some blog posts on the matter, please send them!
I&amp;rsquo;m quite interested.&lt;/p&gt;
&lt;p&gt;If you want to experiment with the code, here&amp;rsquo;s a cart that you can load with TIC-80, or you can &lt;a href=&#34;https://tic80.com/play?cart=4497&#34; target=&#34;_blank&#34;&gt;experiment online&lt;/a&gt; on TIC&amp;rsquo;s website:&lt;/p&gt;
&lt;figure&gt;&lt;img src=&#34;https://andreyor.st/2026-01-09-slopes-in-aabb-collision-systems/aabb-slopes.png&#34;
         alt=&#34;Figure 4: That&amp;amp;rsquo;s an actual game cartridge you can load into TIC-80!&#34;/&gt;&lt;figcaption&gt;
            &lt;p&gt;&lt;span class=&#34;figure-number&#34;&gt;Figure 4: &lt;/span&gt;That&amp;rsquo;s an actual game cartridge you can load into TIC-80!&lt;/p&gt;
        &lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p&gt;That&amp;rsquo;s all from me.
Hope you&amp;rsquo;ve enjoyed this write-up as much as I did, and see you next time!&lt;/p&gt;
&lt;div class=&#34;footnotes&#34; role=&#34;doc-endnotes&#34;&gt;
&lt;hr&gt;
&lt;ol&gt;
&lt;li id=&#34;fn:1&#34;&gt;
&lt;p&gt;Code responsible for controlling the player object - movement, abilities, general game logic&amp;#160;&lt;a href=&#34;#fnref:1&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&#34;fn:2&#34;&gt;
&lt;p&gt;Fennel &lt;strong&gt;is&lt;/strong&gt; Lua, but it adds a few features on top, like macros that allow additional metaprogramming capabilities, and fixes a bunch of Lua&amp;rsquo;s shortcomings.&amp;#160;&lt;a href=&#34;#fnref:2&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&#34;fn:3&#34;&gt;
&lt;p&gt;This looks so much tighter in Fennel, gosh:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-fennel&#34; data-lang=&#34;fennel&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;       (&lt;span style=&#34;font-weight:bold&#34;&gt;each &lt;/span&gt;[&lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;_&lt;/span&gt; &lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;col&lt;/span&gt; &lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;ipairs&lt;/span&gt;(&lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;cols&lt;/span&gt;)]
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;         (&lt;span style=&#34;font-weight:bold&#34;&gt;when &lt;/span&gt;(&lt;span style=&#34;font-weight:bold&#34;&gt;= &lt;/span&gt;&lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;col.normal.y&lt;/span&gt; -1)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;           (&lt;span style=&#34;font-weight:bold&#34;&gt;set &lt;/span&gt;&lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;ball.yvel&lt;/span&gt; &lt;span style=&#34;font-weight:bold&#34;&gt;= &lt;/span&gt;(&lt;span style=&#34;font-weight:bold&#34;&gt;- &lt;/span&gt;(&lt;span style=&#34;font-weight:bold&#34;&gt;* &lt;/span&gt;&lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;ball.yvel&lt;/span&gt; 0.9))))
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;         (&lt;span style=&#34;font-weight:bold&#34;&gt;when &lt;/span&gt;(&lt;span style=&#34;font-weight:bold&#34;&gt;not= &lt;/span&gt;&lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;col.normal.x&lt;/span&gt; 0)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;           (&lt;span style=&#34;font-weight:bold&#34;&gt;set &lt;/span&gt;&lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;ball.xvel&lt;/span&gt; &lt;span style=&#34;font-weight:bold&#34;&gt;= &lt;/span&gt;(&lt;span style=&#34;font-weight:bold&#34;&gt;- &lt;/span&gt;(&lt;span style=&#34;font-weight:bold&#34;&gt;* &lt;/span&gt;&lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;ball.xvel&lt;/span&gt; 0.9)))))
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&amp;#160;&lt;a href=&#34;#fnref:3&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;/div&gt;
&lt;div class=&#34;comment-link&#34;&gt; &lt;a href=&#34;mailto:%61%6e%64%72%65%79%6f%72%73%74%2b%62%6c%6f%67%40%67%6d%61%69%6c%2e%63%6f%6d?subject=Comment: Slopes in AABB collision systems&#34; target=&#34;_blank&#34; rel=&#34;noopener noreferrer&#34;&gt;Comment via email&lt;/a&gt;&lt;/div&gt;</description>
      <pubDate>Fri, 09 Jan 2026 04:11:00 +0300</pubDate>
    </item><item>
      <title>Game4 and Autumn Lisp Game Jam</title>
      <link>https://andreyor.st/posts/2023-10-19-game4-and-autumn-lisp-game-jam/</link>
      <guid>https://andreyor.st/posts/2023-10-19-game4-and-autumn-lisp-game-jam/</guid>
      <description>&lt;p&gt;You might be wondering why there were no posts on the game development marathon I&amp;rsquo;ve been doing.
Maybe you&amp;rsquo;d thought that I gave up after the admittedly underwhelming game3 having no actual game just some basic movement.
And yeah, I felt burned up a lot, and considered skipping a month maybe - but then &lt;a href=&#34;https://itch.io/jam/autumn-lisp-game-jam-2023&#34; target=&#34;_blank&#34;&gt;this happened&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;So I thought - well, I just make the game during the game jam instead, and rest up until the jam begins.
Yes, it means I will technically have less time to do this, but I did spend even less time on game dev last month, so it&amp;rsquo;s fine.
I also got an unplanned vacation, so I guess I&amp;rsquo;ll actually have free time too.&lt;/p&gt;
&lt;p&gt;Though, I lied a bit.
The other reason I didn&amp;rsquo;t do anything is also because the next genre I wanted to work in is a puzzle platformer.
And honestly, I don&amp;rsquo;t know what kind of puzzles to make.
None seem appealing.&lt;/p&gt;
&lt;p&gt;So after a bit of consideration, I&amp;rsquo;ve decided that can try to make something like a &lt;a href=&#34;https://en.wikipedia.org/wiki/Lode_Runner&#34; target=&#34;_blank&#34;&gt;Lode Runner&lt;/a&gt; type of game.
I already have the platformer engine from the very first game in this series, and originally I&amp;rsquo;ve planned that I would make a really simple 2D platformer, then in the subsequent game I will refine and improve it.
Similarly, with the top-down games - first make a simple dungeon crawler, then take the blueprint and make an adventure with it.
Unfortunately, the second game was a flop, and thus if I am to make the fifth game I&amp;rsquo;ll have to do everything from scratch.&lt;/p&gt;
&lt;p&gt;So I&amp;rsquo;ve been thinking how I should handle the game.
I guess I&amp;rsquo;m going to ditch the camera here, so the level fits the screen.
It will make it a bit easier to reason about, and I guess I can always add camera back later if I really need to.&lt;/p&gt;
&lt;p&gt;AI would be an issue though - I&amp;rsquo;ll need to implement some kind of path-finding for the 2D grid with gravity in mind.
In other words, enemies should understand that they can jump down from cliffs if it will bring them on the same elevation with the player.
Additionally, enemies shouldn&amp;rsquo;t just follow the shortest path to the player, as it will make them very predictable.&lt;/p&gt;
&lt;p&gt;And if I have time, I can try putting on a twist to the formula.
Not sure what kind of twist yet, but we&amp;rsquo;ll see I guess.&lt;/p&gt;
&lt;p&gt;The jam starts in ~12 hours, which unfortunately means that it will begin while my work week isn&amp;rsquo;t yet ended.
So I will have one day less already.
Additionally, right now I&amp;rsquo;m in another city, and will lose another day on train to get back home.
So this leaves me with 8 days instead of 10.&lt;/p&gt;
&lt;p&gt;Hopefully it will not be an issue.
Wish me luck!&lt;/p&gt;
&lt;div class=&#34;comment-link&#34;&gt; &lt;a href=&#34;mailto:%61%6e%64%72%65%79%6f%72%73%74%2b%62%6c%6f%67%40%67%6d%61%69%6c%2e%63%6f%6d?subject=Comment: Game4 and Autumn Lisp Game Jam&#34; target=&#34;_blank&#34; rel=&#34;noopener noreferrer&#34;&gt;Comment via email&lt;/a&gt;&lt;/div&gt;</description>
      <pubDate>Thu, 19 Oct 2023 18:42:00 +0300</pubDate>
    </item><item>
      <title>Game3 W?/4</title>
      <link>https://andreyor.st/posts/2023-09-16-game3-w4/</link>
      <guid>https://andreyor.st/posts/2023-09-16-game3-w4/</guid>
      <description>&lt;p&gt;I&amp;rsquo;m working on the next game, although I gave myself an extended period of rest.
After failing to complete the last one I needed to distract myself a bit from this activity, so I worked a bit on Fennel stuff, played some games, and I think I&amp;rsquo;m back in business by now.
I have an interesting thing going on with my productivity - it comes in waves.
What I mean is that I can go onto programming frenzy for several months, and then once I feel burned out, I can just play games for another full month straight.
Though I get tired of games too, and if by that time I feel that I&amp;rsquo;m not ready for programming yet, I can read some technical books, or even just some manga if I still feel too tired to be productive.&lt;/p&gt;
&lt;p&gt;So this five month marathon is a bit hard for me, as I basically have to give up on long periods of rest, and juggle my free time to actually have some time to work on these projects.
While these games are nothing special, I feel like I&amp;rsquo;m still learning new stuff, and thus this is beneficial for me still.
Though, at some I actually thought of dropping the whole idea, and starting a more complex project that I was thinking about for a lot of time already.
But I decided to leave it for the next year, as I still need a lot to learn.
But, back on the topic.&lt;/p&gt;
&lt;p&gt;This post actually marks the end of only the second week of the current game.
How come, you might ask?
To put it simply, I miscalculated a bit, and in reality I had another week on the previous game by the time I dropped it.
So, technically, I could probably finish that game, but the burnout got me, so that&amp;rsquo;s that.&lt;/p&gt;
&lt;p&gt;As for this game, I have a little to show.
As it is a side-scrolling shooter, I designed a basic ship with some shooting modules, and some powerup icons.
I considered to not making any graphics for this one, and doing everything with just primitives, like triangles, circles and so on, but after trying to draw a ship like this I dropped the idea and returned to Pixel Studio.
Here are the results so far:&lt;/p&gt;
&lt;figure class=&#34;pixelart&#34;&gt;&lt;img src=&#34;https://andreyor.st/2023-09-16-game3-w4/sprites.png&#34; width=&#34;100%&#34;/&gt;
&lt;/figure&gt;

&lt;p&gt;The ship turned out to be a bit too big, but I decided that I can make a game with larger assets for a change.
So the enemy also uses 4 sprites, instead of just one, as in my previous games.&lt;/p&gt;
&lt;p&gt;And here are the enemies animated:&lt;/p&gt;
&lt;div class=&#34;grid-gallery&#34;&gt;
  &lt;div class=&#34;grid-gallery-row&#34; style=&#34;display: flex&#34;&gt;
&lt;figure class=&#34;pixelart&#34;&gt;&lt;img src=&#34;https://andreyor.st/2023-09-16-game3-w4/roaming.gif&#34; width=&#34;40%&#34; height=&#34;auto&#34;/&gt;
&lt;/figure&gt;

&lt;figure class=&#34;pixelart&#34;&gt;&lt;img src=&#34;https://andreyor.st/2023-09-16-game3-w4/roaming-2.gif&#34; width=&#34;40%&#34; height=&#34;auto&#34;/&gt;
&lt;/figure&gt;

&lt;/div&gt;
&lt;figcaption&gt;
  &lt;p&gt;Movement animations&lt;/p&gt;
&lt;/figcaption&gt;
&lt;div class=&#34;grid-gallery-row&#34; style=&#34;display: flex&#34;&gt;
&lt;figure class=&#34;pixelart&#34;&gt;&lt;img src=&#34;https://andreyor.st/2023-09-16-game3-w4/exploding.gif&#34; width=&#34;40%&#34; height=&#34;auto&#34;/&gt;
&lt;/figure&gt;

&lt;figure class=&#34;pixelart&#34;&gt;&lt;img src=&#34;https://andreyor.st/2023-09-16-game3-w4/explosion-2.gif&#34; width=&#34;40%&#34; height=&#34;auto&#34;/&gt;
&lt;/figure&gt;

&lt;/div&gt;
&lt;figcaption&gt;
  &lt;p&gt;Explosion animations&lt;/p&gt;
&lt;/figcaption&gt;
&lt;/div&gt;
&lt;p&gt;Obviously, I need to add more enemy variety, with different patterns, but designing enemies is hard.
BTW, if you&amp;rsquo;re curious how I draw things on the phone, here&amp;rsquo;s a time-lapse:&lt;/p&gt;
&lt;p&gt;&lt;video autoplay loop&gt;&lt;source src=&#34;https://andreyor.st/2023-09-16-game3-w4/timelapse.webm&#34; type=&#34;video/webm&#34;&gt;
Your browser does not support the video tag.&lt;/video&gt;&lt;/p&gt;
&lt;p&gt;Though, I usually don&amp;rsquo;t do it in the landscape mode, portrait mode works better for me, especially with a stylus.
This process was recorded in landscape just for viewing convenience.&lt;/p&gt;
&lt;p&gt;On the technical side of things, I already implemented a basic movement with linear interpolation for the flying modules, and implemented some enemy patterns, but there&amp;rsquo;s no actual interactions yet.
I&amp;rsquo;m kinda torn on the idea of including &lt;code&gt;bump.lua&lt;/code&gt; again, as I feel like that this game can be done without any collision library.
At the same time, having a collision library may make things much simpler, so I&amp;rsquo;m still thinking how to approach this game.&lt;/p&gt;
&lt;p&gt;For the &lt;em&gt;record&lt;/em&gt;, I basically dropped the idea of making &lt;em&gt;any&lt;/em&gt; kind of music for these games because I just can&amp;rsquo;t get myself to work on it.
I do plan to take some courses on music composition for future projects, though.
Sound effects are challenging enough already by themselves.&lt;/p&gt;
&lt;p&gt;That&amp;rsquo;s all for this week!
Hopefully, I&amp;rsquo;ll manage to finish the game this time, though I already lost one week.
Let&amp;rsquo;s say, this is week 1/3 instead of 2/4 shall we?
Feels less demotivating this way.&lt;/p&gt;
&lt;div class=&#34;comment-link&#34;&gt; &lt;a href=&#34;mailto:%61%6e%64%72%65%79%6f%72%73%74%2b%62%6c%6f%67%40%67%6d%61%69%6c%2e%63%6f%6d?subject=Comment: Game3 W?/4&#34; target=&#34;_blank&#34; rel=&#34;noopener noreferrer&#34;&gt;Comment via email&lt;/a&gt;&lt;/div&gt;</description>
      <pubDate>Sat, 16 Sep 2023 01:59:00 +0300</pubDate>
    </item><item>
      <title>Game2 W4/4</title>
      <link>https://andreyor.st/posts/2023-08-25-game2-w44/</link>
      <guid>https://andreyor.st/posts/2023-08-25-game2-w44/</guid>
      <description>&lt;p&gt;Well, It&amp;rsquo;s unfortunate, but I couldn&amp;rsquo;t make the game in these 4 weeks.
August is just too much of a pain in terms of the amount of different events - maybe even the busiest month in the whole year, for me personally.
Judging by the commit history, I was able to work only for 11 days out of the 28 days given to me by the challenge - and I tried to do at least some work every day and commit everything I did.
I&amp;rsquo;m saying 11 days, but maybe 5 of these were in this last week.&lt;/p&gt;
&lt;p&gt;This last week was all about level generation and, unfortunately, I failed at it.
I was trying to implement the &lt;a href=&#34;https://andreyor.st/posts/2022-05-10-wave-function-collapse-algorithm-in-clojurescript/&#34;&gt;Wave Function Collapse&lt;/a&gt; algorithm in Lua and for some weird reason, it didn&amp;rsquo;t work.
After three failed attempts, I decided to cheat.
My idea was to go and port my ClojureScript version to Fennel form by form and compile it to Lua after it works.
However, it didn&amp;rsquo;t work as well, no idea why.
For some reason, some branches in my code are just never visited at all, although I did everything the same way, and implemented missing pieces from Clojure&amp;rsquo;s standard library for this code to work.
At least it terminated, but instead generated a complete gibberish:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-text&#34; data-lang=&#34;text&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&amp;gt;&amp;gt; (wfc (gen_world 9 9) coast)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;[[&amp;#34;🟩&amp;#34; &amp;#34;🟩&amp;#34; &amp;#34;🟦&amp;#34; &amp;#34;🟦&amp;#34; &amp;#34;🟩&amp;#34; &amp;#34;🟩&amp;#34; &amp;#34;🟩&amp;#34; &amp;#34;🟦&amp;#34; &amp;#34;🟩&amp;#34;]
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt; [&amp;#34;🟩&amp;#34; &amp;#34;🟩&amp;#34; &amp;#34;🟫&amp;#34; &amp;#34;🟦&amp;#34; &amp;#34;🟫&amp;#34; &amp;#34;🟦&amp;#34; &amp;#34;🟦&amp;#34; &amp;#34;🟩&amp;#34; &amp;#34;🟦&amp;#34;]
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt; [&amp;#34;🟦&amp;#34; &amp;#34;🟩&amp;#34; &amp;#34;🟫&amp;#34; &amp;#34;🟫&amp;#34; &amp;#34;🟦&amp;#34; &amp;#34;🟫&amp;#34; &amp;#34;🟩&amp;#34; &amp;#34;🟫&amp;#34; &amp;#34;🟫&amp;#34;]
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt; [&amp;#34;🟫&amp;#34; &amp;#34;🟫&amp;#34; &amp;#34;🟩&amp;#34; &amp;#34;🟩&amp;#34; &amp;#34;🟩&amp;#34; &amp;#34;🟦&amp;#34; &amp;#34;🟫&amp;#34; &amp;#34;🟦&amp;#34; &amp;#34;🟦&amp;#34;]
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt; [&amp;#34;🟫&amp;#34; &amp;#34;🟫&amp;#34; &amp;#34;🟩&amp;#34; &amp;#34;🟫&amp;#34; &amp;#34;🟩&amp;#34; &amp;#34;🟦&amp;#34; &amp;#34;🟩&amp;#34; &amp;#34;🟩&amp;#34; &amp;#34;🟩&amp;#34;]
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt; [&amp;#34;🟦&amp;#34; &amp;#34;🟦&amp;#34; &amp;#34;🟩&amp;#34; &amp;#34;🟫&amp;#34; &amp;#34;🟦&amp;#34; &amp;#34;🟩&amp;#34; &amp;#34;🟦&amp;#34; &amp;#34;🟩&amp;#34; &amp;#34;🟫&amp;#34;]
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt; [&amp;#34;🟦&amp;#34; &amp;#34;🟫&amp;#34; &amp;#34;🟩&amp;#34; &amp;#34;🟦&amp;#34; &amp;#34;🟦&amp;#34; &amp;#34;🟩&amp;#34; &amp;#34;🟩&amp;#34; &amp;#34;🟩&amp;#34; &amp;#34;🟫&amp;#34;]
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt; [&amp;#34;🟦&amp;#34; &amp;#34;🟩&amp;#34; &amp;#34;🟦&amp;#34; &amp;#34;🟦&amp;#34; &amp;#34;🟩&amp;#34; &amp;#34;🟩&amp;#34; &amp;#34;🟩&amp;#34; &amp;#34;🟫&amp;#34; &amp;#34;🟩&amp;#34;]
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt; [&amp;#34;🟦&amp;#34; &amp;#34;🟩&amp;#34; &amp;#34;🟫&amp;#34; &amp;#34;🟦&amp;#34; &amp;#34;🟦&amp;#34; &amp;#34;🟩&amp;#34; &amp;#34;🟩&amp;#34; &amp;#34;🟦&amp;#34; &amp;#34;🟫&amp;#34;]]
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;It is supposed to look like this:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-text&#34; data-lang=&#34;text&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;user&amp;gt; (wfc (gen-world 9 9) coast)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;[[&amp;#34;🟫&amp;#34; &amp;#34;🟫&amp;#34; &amp;#34;🟫&amp;#34; &amp;#34;🟫&amp;#34; &amp;#34;🟫&amp;#34; &amp;#34;🟫&amp;#34; &amp;#34;🟫&amp;#34; &amp;#34;🟫&amp;#34; &amp;#34;🟫&amp;#34;]
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt; [&amp;#34;🟩&amp;#34; &amp;#34;🟫&amp;#34; &amp;#34;🟫&amp;#34; &amp;#34;🟫&amp;#34; &amp;#34;🟫&amp;#34; &amp;#34;🟫&amp;#34; &amp;#34;🟫&amp;#34; &amp;#34;🟫&amp;#34; &amp;#34;🟫&amp;#34;]
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt; [&amp;#34;🟦&amp;#34; &amp;#34;🟩&amp;#34; &amp;#34;🟫&amp;#34; &amp;#34;🟫&amp;#34; &amp;#34;🟫&amp;#34; &amp;#34;🟫&amp;#34; &amp;#34;🟫&amp;#34; &amp;#34;🟫&amp;#34; &amp;#34;🟫&amp;#34;]
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt; [&amp;#34;🟦&amp;#34; &amp;#34;🟦&amp;#34; &amp;#34;🟩&amp;#34; &amp;#34;🟫&amp;#34; &amp;#34;🟫&amp;#34; &amp;#34;🟫&amp;#34; &amp;#34;🟫&amp;#34; &amp;#34;🟫&amp;#34; &amp;#34;🟩&amp;#34;]
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt; [&amp;#34;🟦&amp;#34; &amp;#34;🟦&amp;#34; &amp;#34;🟦&amp;#34; &amp;#34;🟩&amp;#34; &amp;#34;🟫&amp;#34; &amp;#34;🟫&amp;#34; &amp;#34;🟫&amp;#34; &amp;#34;🟩&amp;#34; &amp;#34;🟦&amp;#34;]
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt; [&amp;#34;🟦&amp;#34; &amp;#34;🟦&amp;#34; &amp;#34;🟦&amp;#34; &amp;#34;🟦&amp;#34; &amp;#34;🟩&amp;#34; &amp;#34;🟫&amp;#34; &amp;#34;🟩&amp;#34; &amp;#34;🟦&amp;#34; &amp;#34;🟦&amp;#34;]
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt; [&amp;#34;🟦&amp;#34; &amp;#34;🟦&amp;#34; &amp;#34;🟦&amp;#34; &amp;#34;🟦&amp;#34; &amp;#34;🟦&amp;#34; &amp;#34;🟩&amp;#34; &amp;#34;🟦&amp;#34; &amp;#34;🟦&amp;#34; &amp;#34;🟦&amp;#34;]
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt; [&amp;#34;🟦&amp;#34; &amp;#34;🟦&amp;#34; &amp;#34;🟦&amp;#34; &amp;#34;🟦&amp;#34; &amp;#34;🟦&amp;#34; &amp;#34;🟦&amp;#34; &amp;#34;🟦&amp;#34; &amp;#34;🟦&amp;#34; &amp;#34;🟦&amp;#34;]
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt; [&amp;#34;🟦&amp;#34; &amp;#34;🟦&amp;#34; &amp;#34;🟦&amp;#34; &amp;#34;🟦&amp;#34; &amp;#34;🟦&amp;#34; &amp;#34;🟦&amp;#34; &amp;#34;🟦&amp;#34; &amp;#34;🟦&amp;#34; &amp;#34;🟦&amp;#34;]]
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;This reminded me of the importance of data structures and their properties I keep forgetting when programming in Clojure.
Because in Clojure it&amp;rsquo;s just the thing you have, so you don&amp;rsquo;t think much about it.&lt;/p&gt;
&lt;p&gt;You see, Clojure has 4 main data structures, baked up with literals: list &lt;code&gt;(a, b)&lt;/code&gt;, map &lt;code&gt;{k1 a, k2 b}&lt;/code&gt;, vector &lt;code&gt;[a, b]&lt;/code&gt;, and set &lt;code&gt;#{a, b}&lt;/code&gt;.
Each of these has its own set of properties, for example, lists are only fast to add elements at the front, and vectors are fast to add elements to the tail.
Maps are mostly like Lua&amp;rsquo;s tables, and sets are like maps in the way that they can only store unique elements, and are indexed like a map but iterated like a list.&lt;/p&gt;
&lt;p&gt;In my ClojureScript implementation of the WFC algorithm, I used all these data structures and relied on their behavior.
For example, the superposition for a cell after the world&amp;rsquo;s initialization is a set of all possible values for the cell:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-clojure&#34; data-lang=&#34;clojure&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;#{&lt;span style=&#34;color:#666;font-style:italic&#34;&gt;&amp;#34;🟦&amp;#34;&lt;/span&gt;, &lt;span style=&#34;color:#666;font-style:italic&#34;&gt;&amp;#34;🟩&amp;#34;&lt;/span&gt;, &lt;span style=&#34;color:#666;font-style:italic&#34;&gt;&amp;#34;🟫&amp;#34;&lt;/span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;I used maps to store the recipe:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-clojure&#34; data-lang=&#34;clojure&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;{&lt;span style=&#34;color:#666;font-style:italic&#34;&gt;&amp;#34;🟫&amp;#34;&lt;/span&gt; {&lt;span style=&#34;color:#666;font-style:italic&#34;&gt;:up&lt;/span&gt; #{&lt;span style=&#34;color:#666;font-style:italic&#34;&gt;&amp;#34;🟫&amp;#34;&lt;/span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;       &lt;span style=&#34;color:#666;font-style:italic&#34;&gt;:down&lt;/span&gt; #{&lt;span style=&#34;color:#666;font-style:italic&#34;&gt;&amp;#34;🟫&amp;#34;&lt;/span&gt; &lt;span style=&#34;color:#666;font-style:italic&#34;&gt;&amp;#34;🟩&amp;#34;&lt;/span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;       &lt;span style=&#34;color:#666;font-style:italic&#34;&gt;:left&lt;/span&gt; #{&lt;span style=&#34;color:#666;font-style:italic&#34;&gt;&amp;#34;🟩&amp;#34;&lt;/span&gt; &lt;span style=&#34;color:#666;font-style:italic&#34;&gt;&amp;#34;🟫&amp;#34;&lt;/span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;       &lt;span style=&#34;color:#666;font-style:italic&#34;&gt;:right&lt;/span&gt; #{&lt;span style=&#34;color:#666;font-style:italic&#34;&gt;&amp;#34;🟫&amp;#34;&lt;/span&gt; &lt;span style=&#34;color:#666;font-style:italic&#34;&gt;&amp;#34;🟩&amp;#34;&lt;/span&gt;}}
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt; &lt;span style=&#34;color:#666;font-style:italic&#34;&gt;&amp;#34;🟩&amp;#34;&lt;/span&gt; {&lt;span style=&#34;color:#666;font-style:italic&#34;&gt;:up&lt;/span&gt; #{&lt;span style=&#34;color:#666;font-style:italic&#34;&gt;&amp;#34;🟫&amp;#34;&lt;/span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;       &lt;span style=&#34;color:#666;font-style:italic&#34;&gt;:down&lt;/span&gt; #{&lt;span style=&#34;color:#666;font-style:italic&#34;&gt;&amp;#34;🟦&amp;#34;&lt;/span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;       &lt;span style=&#34;color:#666;font-style:italic&#34;&gt;:left&lt;/span&gt; #{&lt;span style=&#34;color:#666;font-style:italic&#34;&gt;&amp;#34;🟫&amp;#34;&lt;/span&gt; &lt;span style=&#34;color:#666;font-style:italic&#34;&gt;&amp;#34;🟦&amp;#34;&lt;/span&gt; &lt;span style=&#34;color:#666;font-style:italic&#34;&gt;&amp;#34;🟩&amp;#34;&lt;/span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;       &lt;span style=&#34;color:#666;font-style:italic&#34;&gt;:right&lt;/span&gt; #{&lt;span style=&#34;color:#666;font-style:italic&#34;&gt;&amp;#34;🟫&amp;#34;&lt;/span&gt; &lt;span style=&#34;color:#666;font-style:italic&#34;&gt;&amp;#34;🟦&amp;#34;&lt;/span&gt; &lt;span style=&#34;color:#666;font-style:italic&#34;&gt;&amp;#34;🟩&amp;#34;&lt;/span&gt;}}
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt; &lt;span style=&#34;color:#666;font-style:italic&#34;&gt;&amp;#34;🟦&amp;#34;&lt;/span&gt; {&lt;span style=&#34;color:#666;font-style:italic&#34;&gt;:up&lt;/span&gt; #{&lt;span style=&#34;color:#666;font-style:italic&#34;&gt;&amp;#34;🟩&amp;#34;&lt;/span&gt; &lt;span style=&#34;color:#666;font-style:italic&#34;&gt;&amp;#34;🟦&amp;#34;&lt;/span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;       &lt;span style=&#34;color:#666;font-style:italic&#34;&gt;:down&lt;/span&gt; #{&lt;span style=&#34;color:#666;font-style:italic&#34;&gt;&amp;#34;🟦&amp;#34;&lt;/span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;       &lt;span style=&#34;color:#666;font-style:italic&#34;&gt;:left&lt;/span&gt; #{&lt;span style=&#34;color:#666;font-style:italic&#34;&gt;&amp;#34;🟩&amp;#34;&lt;/span&gt; &lt;span style=&#34;color:#666;font-style:italic&#34;&gt;&amp;#34;🟦&amp;#34;&lt;/span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;       &lt;span style=&#34;color:#666;font-style:italic&#34;&gt;:right&lt;/span&gt; #{&lt;span style=&#34;color:#666;font-style:italic&#34;&gt;&amp;#34;🟦&amp;#34;&lt;/span&gt; &lt;span style=&#34;color:#666;font-style:italic&#34;&gt;&amp;#34;🟩&amp;#34;&lt;/span&gt;}}}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;The map itself stores maps with sets that are later used in the algorithm.
Other data structures are at play for other parts of the whole process, but I&amp;rsquo;m not going to touch them here.&lt;/p&gt;
&lt;p&gt;Lua on the other hand has only one collection type - the table.
It can serve as a vector, to which you can add elements at the tail, and you can use them as hash tables, where there can only be a single instance of a given key.
Sets can also be represented if we just use a table where keys are elements and values are the same elements or something like &lt;code&gt;true&lt;/code&gt;.&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-lua&#34; data-lang=&#34;lua&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;font-weight:bold;font-style:italic&#34;&gt;local&lt;/span&gt; superposition = {[&lt;span style=&#34;color:#666;font-style:italic&#34;&gt;&amp;#34;🟦&amp;#34;&lt;/span&gt;]=&lt;span style=&#34;font-weight:bold&#34;&gt;true&lt;/span&gt;, [&lt;span style=&#34;color:#666;font-style:italic&#34;&gt;&amp;#34;🟩&amp;#34;&lt;/span&gt;]=&lt;span style=&#34;font-weight:bold&#34;&gt;true&lt;/span&gt;, [&lt;span style=&#34;color:#666;font-style:italic&#34;&gt;&amp;#34;🟫&amp;#34;&lt;/span&gt;]=&lt;span style=&#34;font-weight:bold&#34;&gt;true&lt;/span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;This is actually what the &amp;ldquo;Programming in Lua&amp;rdquo; book &lt;a href=&#34;http://www.lua.org/pil/11.5.html&#34; target=&#34;_blank&#34;&gt;suggests&lt;/a&gt;.
The recipe then looks like this:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-lua&#34; data-lang=&#34;lua&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#888;font-style:italic&#34;&gt;-- Sorry, not sorry for formatting - all these brackets on separate&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#888;font-style:italic&#34;&gt;-- lines take a stupidly big amount of vertical space&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;{[&lt;span style=&#34;color:#666;font-style:italic&#34;&gt;&amp;#34;🟫&amp;#34;&lt;/span&gt;]={up={[&lt;span style=&#34;color:#666;font-style:italic&#34;&gt;&amp;#34;🟫&amp;#34;&lt;/span&gt;]=&lt;span style=&#34;font-weight:bold&#34;&gt;true&lt;/span&gt;},
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;         down={[&lt;span style=&#34;color:#666;font-style:italic&#34;&gt;&amp;#34;🟫&amp;#34;&lt;/span&gt;]=&lt;span style=&#34;font-weight:bold&#34;&gt;true&lt;/span&gt;, [&lt;span style=&#34;color:#666;font-style:italic&#34;&gt;&amp;#34;🟩&amp;#34;&lt;/span&gt;]=&lt;span style=&#34;font-weight:bold&#34;&gt;true&lt;/span&gt;},
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;         left={[&lt;span style=&#34;color:#666;font-style:italic&#34;&gt;&amp;#34;🟩&amp;#34;&lt;/span&gt;]=&lt;span style=&#34;font-weight:bold&#34;&gt;true&lt;/span&gt;, [&lt;span style=&#34;color:#666;font-style:italic&#34;&gt;&amp;#34;🟫&amp;#34;&lt;/span&gt;]=&lt;span style=&#34;font-weight:bold&#34;&gt;true&lt;/span&gt;},
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;         right={[&lt;span style=&#34;color:#666;font-style:italic&#34;&gt;&amp;#34;🟫&amp;#34;&lt;/span&gt;]=&lt;span style=&#34;font-weight:bold&#34;&gt;true&lt;/span&gt;, [&lt;span style=&#34;color:#666;font-style:italic&#34;&gt;&amp;#34;🟩&amp;#34;&lt;/span&gt;]=&lt;span style=&#34;font-weight:bold&#34;&gt;true&lt;/span&gt;}},
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt; [&lt;span style=&#34;color:#666;font-style:italic&#34;&gt;&amp;#34;🟩&amp;#34;&lt;/span&gt;]={up={[&lt;span style=&#34;color:#666;font-style:italic&#34;&gt;&amp;#34;🟫&amp;#34;&lt;/span&gt;]=&lt;span style=&#34;font-weight:bold&#34;&gt;true&lt;/span&gt;},
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;         down={[&lt;span style=&#34;color:#666;font-style:italic&#34;&gt;&amp;#34;🟦&amp;#34;&lt;/span&gt;]=&lt;span style=&#34;font-weight:bold&#34;&gt;true&lt;/span&gt;},
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;         left={[&lt;span style=&#34;color:#666;font-style:italic&#34;&gt;&amp;#34;🟫&amp;#34;&lt;/span&gt;]=&lt;span style=&#34;font-weight:bold&#34;&gt;true&lt;/span&gt;, [&lt;span style=&#34;color:#666;font-style:italic&#34;&gt;&amp;#34;🟦&amp;#34;&lt;/span&gt;]=&lt;span style=&#34;font-weight:bold&#34;&gt;true&lt;/span&gt;, [&lt;span style=&#34;color:#666;font-style:italic&#34;&gt;&amp;#34;🟩&amp;#34;&lt;/span&gt;]=&lt;span style=&#34;font-weight:bold&#34;&gt;true&lt;/span&gt;},
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;         right={[&lt;span style=&#34;color:#666;font-style:italic&#34;&gt;&amp;#34;🟫&amp;#34;&lt;/span&gt;]=&lt;span style=&#34;font-weight:bold&#34;&gt;true&lt;/span&gt;, [&lt;span style=&#34;color:#666;font-style:italic&#34;&gt;&amp;#34;🟦&amp;#34;&lt;/span&gt;]=&lt;span style=&#34;font-weight:bold&#34;&gt;true&lt;/span&gt;, [&lt;span style=&#34;color:#666;font-style:italic&#34;&gt;&amp;#34;🟩&amp;#34;&lt;/span&gt;]=&lt;span style=&#34;font-weight:bold&#34;&gt;true&lt;/span&gt;}},
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt; [&lt;span style=&#34;color:#666;font-style:italic&#34;&gt;&amp;#34;🟦&amp;#34;&lt;/span&gt;]={up={[&lt;span style=&#34;color:#666;font-style:italic&#34;&gt;&amp;#34;🟩&amp;#34;&lt;/span&gt;]=&lt;span style=&#34;font-weight:bold&#34;&gt;true&lt;/span&gt;, [&lt;span style=&#34;color:#666;font-style:italic&#34;&gt;&amp;#34;🟦&amp;#34;&lt;/span&gt;]=&lt;span style=&#34;font-weight:bold&#34;&gt;true&lt;/span&gt;},
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;         down={[&lt;span style=&#34;color:#666;font-style:italic&#34;&gt;&amp;#34;🟦&amp;#34;&lt;/span&gt;]=&lt;span style=&#34;font-weight:bold&#34;&gt;true&lt;/span&gt;},
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;         left={[&lt;span style=&#34;color:#666;font-style:italic&#34;&gt;&amp;#34;🟩&amp;#34;&lt;/span&gt;]=&lt;span style=&#34;font-weight:bold&#34;&gt;true&lt;/span&gt;, [&lt;span style=&#34;color:#666;font-style:italic&#34;&gt;&amp;#34;🟦&amp;#34;&lt;/span&gt;]=&lt;span style=&#34;font-weight:bold&#34;&gt;true&lt;/span&gt;},
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;         right={[&lt;span style=&#34;color:#666;font-style:italic&#34;&gt;&amp;#34;🟦&amp;#34;&lt;/span&gt;]=&lt;span style=&#34;font-weight:bold&#34;&gt;true&lt;/span&gt;, [&lt;span style=&#34;color:#666;font-style:italic&#34;&gt;&amp;#34;🟩&amp;#34;&lt;/span&gt;]=&lt;span style=&#34;font-weight:bold&#34;&gt;true&lt;/span&gt;}}}
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#888;font-style:italic&#34;&gt;-- also, the table syntax is just horrible - why square brackets are&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#888;font-style:italic&#34;&gt;-- needed around strings for keys? Why can&amp;#39;t we write {&amp;#34;foo bar&amp;#34;=&amp;#34;baz&amp;#34;}?&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#888;font-style:italic&#34;&gt;-- And why can we write {foo=&amp;#34;bar&amp;#34;}? Why variable foo is not the same&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#888;font-style:italic&#34;&gt;-- foo when used in the table? Why foo is now a string?  The syntax is&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#888;font-style:italic&#34;&gt;-- too smart for its own good.&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Now, here&amp;rsquo;s the problem - how do we tell apart sets from key-value tables?
We can write some predicates, like &lt;code&gt;is_set&lt;/code&gt; that either check that all values are &lt;code&gt;true&lt;/code&gt;, or create a constructor function that will add a hidden &lt;code&gt;__index&lt;/code&gt; metatable with a &lt;code&gt;is_set&lt;/code&gt; key set to true, but it&amp;rsquo;s all pretty janky, in my opinion at least.
A proper data structure should have its own syntax literal and a set of functions to check the type.
Lua doesn&amp;rsquo;t even give you the ability to distinguish sequential tables from associative ones&lt;sup id=&#34;fnref:1&#34;&gt;&lt;a href=&#34;#fn:1&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;1&lt;/a&gt;&lt;/sup&gt;.
I don&amp;rsquo;t think that Clojure&amp;rsquo;s choice of &lt;code&gt;#{}&lt;/code&gt; for sets is great, but it&amp;rsquo;s a lot better than nothing.&lt;/p&gt;
&lt;p&gt;But there&amp;rsquo;s also another issue with our implementation of sets - iteration support.
In Clojure, you can iterate over any type of data structure, and it will actually do it in a meaningful way.&lt;/p&gt;
&lt;p&gt;If you try to iterate over a list, vector, or set - it will just iterate over the elements:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-clojure&#34; data-lang=&#34;clojure&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;user&amp;gt;&lt;/span&gt; (&lt;span style=&#34;font-weight:bold;font-style:italic&#34;&gt;map identity &lt;/span&gt;&amp;#39;(&lt;span style=&#34;color:#666;font-style:italic&#34;&gt;:a&lt;/span&gt; &lt;span style=&#34;color:#666;font-style:italic&#34;&gt;:b&lt;/span&gt; &lt;span style=&#34;color:#666;font-style:italic&#34;&gt;:c&lt;/span&gt;))
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;(&lt;span style=&#34;color:#666;font-style:italic&#34;&gt;:a&lt;/span&gt; &lt;span style=&#34;color:#666;font-style:italic&#34;&gt;:b&lt;/span&gt; &lt;span style=&#34;color:#666;font-style:italic&#34;&gt;:c&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;user&amp;gt;&lt;/span&gt; (&lt;span style=&#34;font-weight:bold;font-style:italic&#34;&gt;map identity &lt;/span&gt;[&lt;span style=&#34;color:#666;font-style:italic&#34;&gt;:a&lt;/span&gt; &lt;span style=&#34;color:#666;font-style:italic&#34;&gt;:b&lt;/span&gt; &lt;span style=&#34;color:#666;font-style:italic&#34;&gt;:c&lt;/span&gt;])
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;(&lt;span style=&#34;color:#666;font-style:italic&#34;&gt;:a&lt;/span&gt; &lt;span style=&#34;color:#666;font-style:italic&#34;&gt;:b&lt;/span&gt; &lt;span style=&#34;color:#666;font-style:italic&#34;&gt;:c&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;user&amp;gt;&lt;/span&gt; (&lt;span style=&#34;font-weight:bold;font-style:italic&#34;&gt;map identity &lt;/span&gt;#{&lt;span style=&#34;color:#666;font-style:italic&#34;&gt;:a&lt;/span&gt; &lt;span style=&#34;color:#666;font-style:italic&#34;&gt;:b&lt;/span&gt; &lt;span style=&#34;color:#666;font-style:italic&#34;&gt;:c&lt;/span&gt;})
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;(&lt;span style=&#34;color:#666;font-style:italic&#34;&gt;:b&lt;/span&gt; &lt;span style=&#34;color:#666;font-style:italic&#34;&gt;:c&lt;/span&gt; &lt;span style=&#34;color:#666;font-style:italic&#34;&gt;:a&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;In the case of a set the order is not determined, so the order of results is arbitrary.
If you try to iterate over a map though, it&amp;rsquo;s a bit different:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-clojure&#34; data-lang=&#34;clojure&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;user&amp;gt;&lt;/span&gt; (&lt;span style=&#34;font-weight:bold;font-style:italic&#34;&gt;map identity &lt;/span&gt;{&lt;span style=&#34;color:#666;font-style:italic&#34;&gt;:a&lt;/span&gt; &lt;span style=&#34;color:#666;font-style:italic&#34;&gt;&amp;#34;b&amp;#34;&lt;/span&gt;, &lt;span style=&#34;color:#666;font-style:italic&#34;&gt;:c&lt;/span&gt; &lt;span style=&#34;color:#666;font-style:italic&#34;&gt;&amp;#34;d&amp;#34;&lt;/span&gt;, &lt;span style=&#34;color:#666;font-style:italic&#34;&gt;:e&lt;/span&gt; &lt;span style=&#34;color:#666;font-style:italic&#34;&gt;&amp;#34;f&amp;#34;&lt;/span&gt;})
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;([&lt;span style=&#34;color:#666;font-style:italic&#34;&gt;:c&lt;/span&gt; &lt;span style=&#34;color:#666;font-style:italic&#34;&gt;&amp;#34;d&amp;#34;&lt;/span&gt;] [&lt;span style=&#34;color:#666;font-style:italic&#34;&gt;:a&lt;/span&gt; &lt;span style=&#34;color:#666;font-style:italic&#34;&gt;&amp;#34;b&amp;#34;&lt;/span&gt;] [&lt;span style=&#34;color:#666;font-style:italic&#34;&gt;:e&lt;/span&gt; &lt;span style=&#34;color:#666;font-style:italic&#34;&gt;&amp;#34;f&amp;#34;&lt;/span&gt;])
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Same as for the set, the order is arbitrary, but you can see that the elements in the list are vectors.
That&amp;rsquo;s right, when we iterate over the map we get back vectors, containing the key and its value, which we can then use in the function that we iterate with.&lt;/p&gt;
&lt;p&gt;You may be thinking: &amp;ldquo;Well, that&amp;rsquo;s the same as what &lt;code&gt;pairs&lt;/code&gt; does in Lua&amp;rdquo; but it&amp;rsquo;s not.
Unlike what I have shown to you, &lt;code&gt;pairs&lt;/code&gt; will &lt;strong&gt;always&lt;/strong&gt; return the key and value, even for vectors.
Yes, in the case of a vector, the key will be an index, and we can pretty much ignore it most of the time, but what about our sets?
In the case of our set implementation, the key is actually the value we&amp;rsquo;re looking for, because the value is just &lt;code&gt;true&lt;/code&gt;.
So no, implementing sets as tables that store &lt;code&gt;true&lt;/code&gt; as a value is a no-go for most use cases, we have to store the value both as a key and a value in a set:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-lua&#34; data-lang=&#34;lua&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;font-weight:bold;font-style:italic&#34;&gt;local&lt;/span&gt; superposition = {[&lt;span style=&#34;color:#666;font-style:italic&#34;&gt;&amp;#34;🟦&amp;#34;&lt;/span&gt;]=&lt;span style=&#34;color:#666;font-style:italic&#34;&gt;&amp;#34;🟦&amp;#34;&lt;/span&gt;, [&lt;span style=&#34;color:#666;font-style:italic&#34;&gt;&amp;#34;🟩&amp;#34;&lt;/span&gt;]=&lt;span style=&#34;color:#666;font-style:italic&#34;&gt;&amp;#34;🟩&amp;#34;&lt;/span&gt;, [&lt;span style=&#34;color:#666;font-style:italic&#34;&gt;&amp;#34;🟫&amp;#34;&lt;/span&gt;]=&lt;span style=&#34;color:#666;font-style:italic&#34;&gt;&amp;#34;🟫&amp;#34;&lt;/span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;But I digress.&lt;/p&gt;
&lt;p&gt;Another, very important property of Clojure&amp;rsquo;s data structures is that they&amp;rsquo;re both immutable and persistent.
This means that whatever we do with the vector doesn&amp;rsquo;t actually change the vector - it returns a new vector that shares the unchanged part of the structure with the old one plus new stuff.
Clojure author went to great lengths to make this process fast and reliable.&lt;/p&gt;
&lt;p&gt;I used this heavily in the CLJS version of the algorithm.
Perhaps too heavily, but it&amp;rsquo;s a different topic.
What it gave me though is the ability to change the world without mutating the original world, so every step of the algorithm actually produced a new world, and I could trace it back to the very beginning if I wanted to.
It is not as fast as the mutation-based approach but is much more sane to work with and debug.&lt;/p&gt;
&lt;p&gt;My first implementation of the algorithm in Lua, however, was mutating the world.
Not only it is hard to debug, Lua makes it harder because you can&amp;rsquo;t view the data structure you&amp;rsquo;re working with.
You probably don&amp;rsquo;t remember it, but I made a &lt;a href=&#34;https://andreyor.st/posts/2021-01-09-pretty-printing-for-fennel-language/&#34;&gt;complete rewrite&lt;/a&gt; of the pretty printer for the Fennel language, and I did it mostly because the original one produced the representation that was hard to read due to indentation going all over the place.
Well, guess what, Lua doesn&amp;rsquo;t have its own pretty printer whatsoever.
You have to write your own or import some library that does it for you, which is not feasible with TIC-80, as its screen resolution is too small to comfortably read anything, especially huge tables that contain lots of elements, like the world I was trying to generate.
Can you feel my frustration?&lt;/p&gt;
&lt;p&gt;Then I made a hacky &lt;code&gt;deepcopy&lt;/code&gt; function (another part that lacks from Lua because &lt;em&gt;tables&lt;/em&gt;&lt;sup id=&#34;fnref:2&#34;&gt;&lt;a href=&#34;#fn:2&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;2&lt;/a&gt;&lt;/sup&gt;) to at least get a copy-on-write type of immutability in Lua.
This slowed down things to the point that it was useless for the sizes of worlds I wanted to construct.
And it didn&amp;rsquo;t fix the problem, so it was somewhere else.
I wasn&amp;rsquo;t ready to re-implement the whole thing again, so I stopped here.&lt;/p&gt;
&lt;p&gt;Another thing I keep forgetting is how great Clojure&amp;rsquo;s standard library for manipulating collections is.
There are all sorts of operations on sets, you can transform maps, iterate on anything, rename keys, etc., etc.
Lua has a very rudimentary library for tables, that can&amp;rsquo;t do almost anything.
Sure, there are libraries like &lt;a href=&#34;https://github.com/lunarmodules/Penlight&#34; target=&#34;_blank&#34;&gt;Penlight&lt;/a&gt;&lt;sup id=&#34;fnref:3&#34;&gt;&lt;a href=&#34;#fn:3&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;3&lt;/a&gt;&lt;/sup&gt;, and &lt;a href=&#34;https://github.com/lua-stdlib/lua-stdlib&#34; target=&#34;_blank&#34;&gt;lua-stdlib&lt;/a&gt; that could help with that, and I made a &lt;a href=&#34;https://gitlab.com/andreyorst/fennel-cljlib&#34; target=&#34;_blank&#34;&gt;port&lt;/a&gt; of Clojure&amp;rsquo;s core namespace to Fennel (and therefore Lua) but again - TIC-80 makes it hard to use these libraries.
So, no, Lua isn&amp;rsquo;t great at manipulating data structures, and porting Clojure code to it is a pain.
What did I expect?
Well, I&amp;rsquo;ve ported all the functions I used in my CLJS WFC implementation, things like &lt;code&gt;map&lt;/code&gt;, &lt;code&gt;keep&lt;/code&gt;, &lt;code&gt;map-indexed&lt;/code&gt;, &lt;code&gt;get-in&lt;/code&gt;, &lt;code&gt;assoc-in&lt;/code&gt;, etc., but there were still problems with how to iterate over tables, as there is no sequence abstraction in Lua, and it&amp;rsquo;s hard to make it properly.
I tried multiple times before, and the best I got was my &lt;a href=&#34;https://gitlab.com/andreyorst/lazy-seq&#34; target=&#34;_blank&#34;&gt;lazy sequence&lt;/a&gt; library for Fennel, but even there I took some shortcuts regarding the situation with tables that have both associative and array parts.
Sorry Lua fans, but combined data structures like that are stupid.&lt;/p&gt;
&lt;p&gt;I&amp;rsquo;m writing this a bit early, as there are clearly some more days until the end of this week, but then again, I won&amp;rsquo;t have time to do any work neither this evening, nor tomorrow, or the day after that.
It&amp;rsquo;s a shame though, I don&amp;rsquo;t want to abandon this roguelike idea, and maybe I&amp;rsquo;ll get back to it after the challenge ends.
Or maybe not, I don&amp;rsquo;t know.
I will have another chance to make a top-down game, but probably without an isometric projection.&lt;/p&gt;
&lt;p&gt;Looking back on the development process for this game I can see that choosing the isometric projection was probably the wrong decision.
First, I struggled with the projection itself, then with the projection of screen coordinates back to the world coordinates.
Halfway through I ditched the isometric projection and started drawing stuff without it, in hopes that I will just enable it back when the world generation is ready.
But then the world generation was a problem.
I didn&amp;rsquo;t even get to work on enemies, items, menus, and so on.
Well, a roguelike is a lot more involved than a simple platformer, so it is expected that it takes more time to do stuff, but I didn&amp;rsquo;t have that time unfortunately.&lt;/p&gt;
&lt;p&gt;Lua isn&amp;rsquo;t the one to blame for all these problems though.
While I did get some frustrations with its syntax, overall the experience wasn&amp;rsquo;t that bad.
However, I will not probably use it for upcoming projects.
The &lt;code&gt;lua-mode&lt;/code&gt; package for Emacs is in a rough shape - there is no pairing support for &lt;code&gt;do end&lt;/code&gt; keywords, like in &lt;code&gt;sh-mode&lt;/code&gt; or &lt;code&gt;ruby-mode&lt;/code&gt;.
Indentation is extremely slow and often unpredictable.&lt;/p&gt;
&lt;p&gt;The next game on the list is a Card game but I&amp;rsquo;m not in the mood for that at all, so I&amp;rsquo;m going to change this and make a side-scrolling shoot-em-up kind of game.
It should be much easier, and with all of the frustration built up over the last month, this is probably just what I need.&lt;/p&gt;
&lt;div class=&#34;footnotes&#34; role=&#34;doc-endnotes&#34;&gt;
&lt;hr&gt;
&lt;ol&gt;
&lt;li id=&#34;fn:1&#34;&gt;
&lt;p&gt;I know that the distinction isn&amp;rsquo;t possible because the data structure combines both sequential and associative parts in one package, but at least Lua could give us some predicates that would check if the associative part has any values and if the sequential part is not empty.
This way we would be able to detect if the table we&amp;rsquo;re looking at only has the array part, or only has the associative part which is much better than nothing.
It can be done in Lua but it is slow and often unreliable.&amp;#160;&lt;a href=&#34;#fnref:1&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&#34;fn:2&#34;&gt;
&lt;p&gt;It is hard to properly implement deep copying because of metatables.&amp;#160;&lt;a href=&#34;#fnref:2&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&#34;fn:3&#34;&gt;
&lt;p&gt;I keep forgetting its name and every single time I have to search for it as &amp;ldquo;lua batteries included&amp;rdquo; to only find a reddit post with a link to the repo that just lists lots of libraries for Lua.&amp;#160;&lt;a href=&#34;#fnref:3&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/div&gt;
&lt;div class=&#34;comment-link&#34;&gt; &lt;a href=&#34;mailto:%61%6e%64%72%65%79%6f%72%73%74%2b%62%6c%6f%67%40%67%6d%61%69%6c%2e%63%6f%6d?subject=Comment: Game2 W4/4&#34; target=&#34;_blank&#34; rel=&#34;noopener noreferrer&#34;&gt;Comment via email&lt;/a&gt;&lt;/div&gt;</description>
      <pubDate>Fri, 25 Aug 2023 18:35:00 +0300</pubDate>
    </item><item>
      <title>Game2 W3/4</title>
      <link>https://andreyor.st/posts/2023-08-20-game2-w34/</link>
      <guid>https://andreyor.st/posts/2023-08-20-game2-w34/</guid>
      <description>&lt;p&gt;Why is it the third week that I finally gain any interest in actually working on the game?
Now, when I think of it, this may be the whole reason I couldn&amp;rsquo;t get into game dev during earlier attempts in the past years.
I consider myself an OK programmer and I know a bit of math that is required for making basic games, so I doubt it was due to a lack of knowledge.
Maybe it is that by the third week, there are some actual results that produce enough stimulation to my brain making it act as a feedback loop that amplifies the feeling until it burns out, and I just never stuck enough for that to trigger before.
Well, I experienced something like that during my first game jam, but not so much after when I made further attempts at this.
I guess forcing myself was the right decision.&lt;/p&gt;
&lt;p&gt;Anyways.
I mostly figured out the map problem I talked about in the previous post and the whole thing started moving, like, literally.
I ported the camera into this project and made a really basic way of moving in the world and it already feels like a game:&lt;/p&gt;
&lt;p&gt;&lt;video autoplay loop muted&gt;&lt;source src=&#34;https://andreyor.st/2023-08-20-game2-w34/camera.webm&#34; type=&#34;video/webm&#34;&gt;
Your browser does not support the video tag.&lt;/video&gt;&lt;/p&gt;
&lt;p&gt;This, however, didn&amp;rsquo;t go as smoothly, as the previous time.
I&amp;rsquo;m still figuring out the quirks, as my rendering pipeline is kinda weird.
You see, in the previous post, I made it so the tiles, used to build up a level, are baked into the cart&amp;rsquo;s map memory block.
This way I don&amp;rsquo;t have to recalculate projections of every floor tile on every tic and can do culling, drawing only what&amp;rsquo;s really is on screen, plus some extra boundary for smooth camera movement.
However, it means that when projecting to the level I need to account for the map offset, somehow.
This turned out to be a bit more challenging than I thought.&lt;/p&gt;
&lt;p&gt;For now, I put that problem aside, as I can always get back to it when I have more complex levels, and I needed to figure out the opposite of what I was doing in the previous post - project screen coordinates back to the world&amp;rsquo;s coordinates.
Because this game is meant to be a roguelike I&amp;rsquo;m going to use the mouse in this game for movement with some automatic path-finding.
Path-finding will have to wait though.&lt;/p&gt;
&lt;p&gt;After a bit of trial and error, I got it working:&lt;/p&gt;
&lt;figure class=&#34;pixelart&#34;&gt;&lt;img src=&#34;https://andreyor.st/2023-08-20-game2-w34/movement.gif&#34;/&gt;
&lt;/figure&gt;

&lt;p&gt;But again, this only works either for the static camera position, or a specific level geometry because of all the magic numbers spread through my code.
I didn&amp;rsquo;t have much time this week, only the first half, so I basically did nothing starting from Wednesday.
While it&amp;rsquo;s a shame, Starting next week I&amp;rsquo;m on vacation and hopefully will be able to spend much more time on the game and finish it in time.
Or at least get all of the mechanics working, and publish it as another demo.&lt;/p&gt;
&lt;p&gt;So, next up - world generation.
I looked at some interesting algorithms for dungeon generation in 2D roguelike games and while many of them are interesting in their own way, I don&amp;rsquo;t want to complicate things that much.
So instead, I turned to my older project - wave function collapse.&lt;/p&gt;
&lt;p&gt;I&amp;rsquo;m going a bit ahead of myself here as I haven&amp;rsquo;t started working on this yet, but I thought that it will be a nice and organic way to generate small, fixed-size levels.
But a plain WFC won&amp;rsquo;t work for me, so I made some adjustments.
The basic idea is to choose some points on the finite grid and assign them to specific elements.&lt;/p&gt;
&lt;p&gt;Here&amp;rsquo;s a janky drawing with all of this in place:&lt;/p&gt;
&lt;figure class=&#34;invertable&#34;&gt;&lt;img src=&#34;https://andreyor.st/2023-08-20-game2-w34/generation.jpg&#34; width=&#34;50%&#34; height=&#34;auto&#34;/&gt;
&lt;/figure&gt;

&lt;p&gt;These objects in the drawing are:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Four rotations of room exits, connected to a floor tile, corridor tile, and two wall tiles;&lt;/li&gt;
&lt;li&gt;level entry and level exit, connected to 4 floor tiles at each side;&lt;/li&gt;
&lt;li&gt;spawn tile, also connected to 4 floor tiles.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;To evenly distribute things in the level, I decided to divide the world into four sectors shown above as a gray grid.
Each sector can have only so many objects to hold, with the exception of entries and exits - these are shared across all segments and there can be only one of each.&lt;/p&gt;
&lt;p&gt;Once objects are in place, the actual WFC takes over and fills the world.
The blue walls on the image and black corridor paths will be generated by WFC, and because it will not pick empty tiles by itself, I suppose isolated rooms won&amp;rsquo;t appear.
The nice part is that the rules can be extremely simple, as there are basically 4 types of tiles used, unlike my previous project.
Of course, I can add variety later.
But that&amp;rsquo;s the topic for the next week.&lt;/p&gt;
&lt;div class=&#34;comment-link&#34;&gt; &lt;a href=&#34;mailto:%61%6e%64%72%65%79%6f%72%73%74%2b%62%6c%6f%67%40%67%6d%61%69%6c%2e%63%6f%6d?subject=Comment: Game2 W3/4&#34; target=&#34;_blank&#34; rel=&#34;noopener noreferrer&#34;&gt;Comment via email&lt;/a&gt;&lt;/div&gt;</description>
      <pubDate>Sun, 20 Aug 2023 21:02:00 +0300</pubDate>
    </item><item>
      <title>Game2 W2/4</title>
      <link>https://andreyor.st/posts/2023-08-13-game2-w24/</link>
      <guid>https://andreyor.st/posts/2023-08-13-game2-w24/</guid>
      <description>&lt;p&gt;Didn&amp;rsquo;t have much progress on the Game2 this week.
Mostly worked on some additional assets, and rendering the world into an isometric grid.&lt;/p&gt;
&lt;p&gt;In the last post, I mentioned that it is very hard to come up with floor tiles that leave enough colors for things to be seen.
I didn&amp;rsquo;t mention it in the previous post, but here are the original tiles I drew for the game to represent the floor and walls:&lt;/p&gt;
&lt;figure class=&#34;pixelart&#34;&gt;&lt;img src=&#34;https://andreyor.st/2023-08-13-game2-w24/old-floor-wall-tiles.png&#34;/&gt;
&lt;/figure&gt;

&lt;p&gt;That&amp;rsquo;s right, I didn&amp;rsquo;t plan on using isometry originally.
However, this looked blunt and there wasn&amp;rsquo;t enough space to add any decoration.
I could, of course, bump the floor tile size to 4 tiles, but it started to look way too big.
Isometry, however, looks decent in my opinion:&lt;/p&gt;
&lt;figure class=&#34;pixelart&#34;&gt;&lt;img src=&#34;https://andreyor.st/2023-08-13-game2-w24/Screenshot_20230812_112859_Pixel%20Studio.jpg&#34; width=&#34;50%&#34; height=&#34;auto&#34;/&gt;
&lt;/figure&gt;

&lt;p&gt;The only problem though is how do I draw this?&lt;/p&gt;
&lt;p&gt;I created a sample level as a two-dimensional array and a simple projection function to project the world&amp;rsquo;s coordinates to screen coordinates:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-lua&#34; data-lang=&#34;lua&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;font-weight:bold;font-style:italic&#34;&gt;local&lt;/span&gt; level = {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  {LW,RW,RW,RW,RW,RW,RW,RW,RW,RW},
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  {LW,F, F, F, F, F, F, F, F, F},
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  {LW,F, F, F, F, F, F, F, F, F},
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  {LW,F, F, F, F, F, F, F, F, F},
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  {LW,F, F, F, F, F, F, F, F, F},
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  {LW,F, F, F, LW,F, F, F, F, F},
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  {LW,F, F, F, RW,F, F, F, F, F},
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  {LW,F, F, F, F, F, F, F, F, F},
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  {LW,F, F, F, F, F, F, F, F, F},
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  {LW,F, F, F, F, F, F, F, F, F},
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  {LW,F, F, F, F, F, F, F, F, F},
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;font-weight:bold;font-style:italic&#34;&gt;local&lt;/span&gt; &lt;span style=&#34;font-weight:bold&#34;&gt;function&lt;/span&gt; &lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;isometric_project&lt;/span&gt; (x, y, w, h)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;font-weight:bold&#34;&gt;return&lt;/span&gt; (x*8)+(y*-8), (x*4)+(y*4)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;font-weight:bold&#34;&gt;end&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;&lt;code&gt;LW&lt;/code&gt; is Left Wall, &lt;code&gt;RW&lt;/code&gt; is a right wall, and &lt;code&gt;F&lt;/code&gt; is floor.
All are objects with a single &lt;code&gt;draw&lt;/code&gt; method, that knows how to draw the tiles.&lt;/p&gt;
&lt;p&gt;I then drew this to the screen with a simple nested loop:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-lua&#34; data-lang=&#34;lua&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;font-weight:bold;font-style:italic&#34;&gt;local&lt;/span&gt; &lt;span style=&#34;font-weight:bold&#34;&gt;function&lt;/span&gt; &lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;load_level&lt;/span&gt; (level)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;font-weight:bold;font-style:italic&#34;&gt;local&lt;/span&gt; ox,oy=x,y
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;font-weight:bold;font-style:italic&#34;&gt;local&lt;/span&gt; offx,offy=120,8
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;font-weight:bold&#34;&gt;for&lt;/span&gt; y=1,#level &lt;span style=&#34;font-weight:bold&#34;&gt;do&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;font-weight:bold&#34;&gt;for&lt;/span&gt; x=1,#level[y] &lt;span style=&#34;font-weight:bold&#34;&gt;do&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      &lt;span style=&#34;font-weight:bold;font-style:italic&#34;&gt;local&lt;/span&gt; t=level[y][x]
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      &lt;span style=&#34;font-weight:bold&#34;&gt;if&lt;/span&gt; &lt;span style=&#34;color:#666;font-style:italic&#34;&gt;&amp;#34;table&amp;#34;&lt;/span&gt; == type(t) &lt;span style=&#34;font-weight:bold&#34;&gt;then&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;font-weight:bold;font-style:italic&#34;&gt;local&lt;/span&gt; mx,my=isometric_project(x-1,y-1)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        t.draw(mx+ox+offx,my+offy+oy)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      &lt;span style=&#34;font-weight:bold&#34;&gt;end&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;font-weight:bold&#34;&gt;end&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;font-weight:bold&#34;&gt;end&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;font-weight:bold&#34;&gt;end&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;This gives me the correct result, albeit there&amp;rsquo;s no special code to draw this with composite tiles, so walls correctly intersect with ordinary tiles:&lt;/p&gt;
&lt;figure class=&#34;pixelart&#34;&gt;&lt;img src=&#34;https://andreyor.st/2023-08-13-game2-w24/wip-render.png&#34;/&gt;
&lt;/figure&gt;

&lt;p&gt;While this works, there&amp;rsquo;s a problem.
As can be seen in the &lt;code&gt;level&lt;/code&gt; array, there are only two kinds of walls - left and right ones.
Ultimately, I want to bake this layout into the map, so I can just call &lt;code&gt;map&lt;/code&gt; to render the whole level, and get camera culling as I did in the previous game.
And it isn&amp;rsquo;t easy to see the problem with the isometric grid from the app where I drew these tiles, so let me bring a non-isometric grid in the engine:&lt;/p&gt;
&lt;figure class=&#34;pixelart&#34;&gt;&lt;img src=&#34;https://andreyor.st/2023-08-13-game2-w24/wip-render-w-grid.png&#34;/&gt;
&lt;/figure&gt;

&lt;p&gt;There are some tiles that right now only take up half of a tile vertically, and some walls are drawn with two tiles and some with three tiles.
This means, that I can&amp;rsquo;t just bake this on a tile-by-tile basis into the map with &lt;code&gt;mset&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Here&amp;rsquo;s a closer look at the problem in case you didn&amp;rsquo;t see it:&lt;/p&gt;
&lt;figure class=&#34;pixelart&#34;&gt;&lt;img src=&#34;https://andreyor.st/2023-08-13-game2-w24/grid-problem.png&#34; width=&#34;50%&#34; height=&#34;auto&#34;/&gt;
&lt;/figure&gt;

&lt;p&gt;I, however, only used the two-tiled walls in this picture (marked with a red grid), so because of that I can&amp;rsquo;t bake this into a map, as the tiles I used don&amp;rsquo;t match the map grid.
Still thinking about how to approach this, as there&amp;rsquo;s also a second problem - entities should be able to stay behind the walls, so in reality I can&amp;rsquo;t bake this all to the map, probably only the floor tiles, and I will have to draw the walls separately.
Or via a second map call.
I mean, I &lt;em&gt;can&lt;/em&gt; draw it just like I do now, but this nested for loop bothers me way too much.
When I will add entities and have bigger levels I fear this will become really slow.&lt;/p&gt;
&lt;p&gt;There are probably ways of doing this easied than what I try to do, but this is just how I roll.&lt;/p&gt;
&lt;p&gt;Additionally, I drew even more items, and I decided that for every single-tile item I will draw a bigger one for the equipment menu:&lt;/p&gt;
&lt;figure class=&#34;pixelart&#34;&gt;&lt;img src=&#34;https://andreyor.st/2023-08-13-game2-w24/more-sprites.png&#34;/&gt;
&lt;/figure&gt;

&lt;p&gt;Hopefully, I will figure out the isometry and have time to implement this.&lt;/p&gt;
&lt;div class=&#34;comment-link&#34;&gt; &lt;a href=&#34;mailto:%61%6e%64%72%65%79%6f%72%73%74%2b%62%6c%6f%67%40%67%6d%61%69%6c%2e%63%6f%6d?subject=Comment: Game2 W2/4&#34; target=&#34;_blank&#34; rel=&#34;noopener noreferrer&#34;&gt;Comment via email&lt;/a&gt;&lt;/div&gt;</description>
      <pubDate>Sun, 13 Aug 2023 10:23:00 +0300</pubDate>
    </item><item>
      <title>Game2 W1/4</title>
      <link>https://andreyor.st/posts/2023-08-06-game2-w14/</link>
      <guid>https://andreyor.st/posts/2023-08-06-game2-w14/</guid>
      <description>&lt;p&gt;First week, second game!&lt;/p&gt;
&lt;p&gt;This time I opted to go with plain Lua.
Don&amp;rsquo;t get me wrong, I like Fennel, but I wanted to get a bit more authentic experience.
Apart from having a nicer standard library, Fennel doesn&amp;rsquo;t add anything to the &lt;em&gt;table&lt;/em&gt; in the case of the TIC-80 environment.
E.g. there&amp;rsquo;s no REPL that I can attach to from the editor and change code around, and there&amp;rsquo;s no real need for macros.&lt;/p&gt;
&lt;p&gt;Plus, I&amp;rsquo;d like to remind myself why I appreciate Fennel so much, as things like pattern matching, table comprehensions, and other Fennel-specific features are not available in Lua.
On the other hand, Lua is simpler, and the code being run is what you&amp;rsquo;d expect, not something that a compiler produced.
If you dared to look into the Lua version of the cart that I published at the end of the last post on the Game1 topic, you know that it looks horrific at times.&lt;/p&gt;
&lt;p&gt;So Lua it is.
TIC-80 actually provides even more languages, namely Ruby, JavaScript, MoonScript, Scheme, Squirrel, Wren, Janet, Python, and even plain WASM, I may try to use some of these.
I haven&amp;rsquo;t decided on future games yet though.&lt;/p&gt;
&lt;p&gt;Speaking of languages - why so many?
This is probably the single thing I dislike about TIC-80 and like more in PICO-8 - the latter is only programmable in a subset of Lua.
If I&amp;rsquo;m being honest, I would rather only leave Lua in TIC and ditched everything else - it&amp;rsquo;s much more authentic this way.
Even if that would mean losing direct Fennel support which, while a shame, is OK - we can always AOT.&lt;/p&gt;
&lt;p&gt;I&amp;rsquo;ve quickly gone through the development log, and it seems that the author is pretty generous with adding new languages to the project.
MoonScript was added first (probably), as far as I can see.
There&amp;rsquo;s a still &lt;a href=&#34;https://github.com/nesbox/TIC-80/issues/1471&#34; target=&#34;_blank&#34;&gt;open request&lt;/a&gt; for Nimscript, whatever it is.
Don&amp;rsquo;t get me wrong, the project is nothing less than amazing, I just think less is more when it comes to things like this.
But I digress.&lt;/p&gt;
&lt;p&gt;I&amp;rsquo;ve spent most of my first week thinking about the game and what would it evolve around.
After a bit of consideration, I decided to make a cyber-based roguelike, because there are already way too many traditional magic-based roguelikes.
So instead of a mighty artifact in my game, the character will search for the mighty neural chip that can do any kind of calculations instantly.
I can also try focusing on range battles, which should give a fresh spin on classic roguelikes too.&lt;/p&gt;
&lt;p&gt;I started working on graphics, but then life told me that I should not do anything this week and I had a power outage for quite some time.
Anyhow, this is what I got so far:&lt;/p&gt;
&lt;figure class=&#34;pixelart&#34;&gt;&lt;img src=&#34;https://andreyor.st/2023-08-06-game2-w14/sprites.png&#34;/&gt;
&lt;/figure&gt;

&lt;p&gt;I also tried making some floor tiles, and once again I feel the limitations of the palette - it&amp;rsquo;s so hard to come up with floor tiles that will make other sprites properly visible.
Maybe I&amp;rsquo;ll add outlines if I will not find a clever solution.&lt;/p&gt;
&lt;p&gt;One such solution might be to divide the palette into two smaller palettes.
In other words, the first 8 colors can be used for interactable objects, and the last 8 for the scenery.
Maybe I&amp;rsquo;ll try that too.
It&amp;rsquo;s even more restrictive, though.&lt;/p&gt;
&lt;p&gt;Unfortunately, I don&amp;rsquo;t think I followed my own word, and the first week went pretty unproductive regarding Game2.
And yes, I will name all of these games like that, sorry!
I did some work on my async library for Fennel, as I found some bugs in it though.
But I&amp;rsquo;ll bring the pace up the next week for sure!&lt;/p&gt;
&lt;div class=&#34;comment-link&#34;&gt; &lt;a href=&#34;mailto:%61%6e%64%72%65%79%6f%72%73%74%2b%62%6c%6f%67%40%67%6d%61%69%6c%2e%63%6f%6d?subject=Comment: Game2 W1/4&#34; target=&#34;_blank&#34; rel=&#34;noopener noreferrer&#34;&gt;Comment via email&lt;/a&gt;&lt;/div&gt;</description>
      <pubDate>Sun, 06 Aug 2023 23:04:00 +0300</pubDate>
    </item><item>
      <title>Game1 - Results</title>
      <link>https://andreyor.st/posts/2023-07-30-game1-results/</link>
      <guid>https://andreyor.st/posts/2023-07-30-game1-results/</guid>
      <description>&lt;p&gt;Well, this was fun!
A bit exhausting, actually.
The first of five months of the challenge has ended and here are the results:&lt;/p&gt;
&lt;iframe src=&#34;https://itch.io/embed-upload/8414750?color=3e3c57&#34;
        allowfullscreen=&#34;&#34;
        width=&#34;600&#34;
        height=&#34;320&#34;
        frameborder=&#34;0&#34;
        style=&#34;margin-left: auto; margin-right: auto; display: block;&#34;&gt;
  &lt;a href=&#34;https://andreyorst.itch.io/game1&#34;&gt;Play Game1 on itch.io&lt;/a&gt;
&lt;/iframe&gt;
&lt;p&gt;The game isn&amp;rsquo;t really complete, but I did my best to make it feel as complete as possible in the time constraints I had.
Technically, I have 1 day left, but given that I need to start working on the next game immediately I decided to give myself a day off.
It only features one level, and cuts out to the &amp;ldquo;you won&amp;rdquo; screen, but I still consider it a success.&lt;/p&gt;
&lt;p&gt;Here&amp;rsquo;s the last bit of devlog for this particular game outlining some things I did since the last post in no particular order.&lt;/p&gt;
&lt;h2 id=&#34;graphic-overhaul&#34;&gt;Graphic overhaul&lt;/h2&gt;
&lt;p&gt;I&amp;rsquo;ve added parallax scrolling to the background, making the game happen inside a cave.
Well, at least, now the background is not that boring to look at, but it&amp;rsquo;s still plain.&lt;/p&gt;
&lt;figure class=&#34;pixelart&#34;&gt;&lt;img src=&#34;https://andreyor.st/2023-07-30-game1-results/background.gif&#34;/&gt;
&lt;/figure&gt;

&lt;p&gt;Then, I added a second layer and started experimenting with different colors:&lt;/p&gt;
&lt;figure class=&#34;pixelart&#34;&gt;&lt;img src=&#34;https://andreyor.st/2023-07-30-game1-results/background2.gif&#34;/&gt;
&lt;/figure&gt;

&lt;p&gt;The problem with limited colors is that I can&amp;rsquo;t find a way of having foreground and background properly separated without using outlines on sprites, which I wanted to avoid for this game at least.
Thus, slimes are now blue instead of green.
But I like it more this way because it pops from the green ground a bit better.
And now that the background is also green it&amp;rsquo;s a lot more sensible.&lt;/p&gt;
&lt;p&gt;I&amp;rsquo;ve spent some time drawing foliage, like mushrooms, trees, grass, water, and so on.
None of this is animated though - time is once again against me.&lt;/p&gt;
&lt;figure class=&#34;pixelart&#34;&gt;&lt;img src=&#34;https://andreyor.st/2023-07-30-game1-results/foliage.png&#34;/&gt;
&lt;/figure&gt;

&lt;p&gt;Also, added the menus, title screen, cover art, and some fade-in/out animations for smoother transitions:&lt;/p&gt;
&lt;figure class=&#34;pixelart&#34;&gt;&lt;img src=&#34;https://andreyor.st/2023-07-30-game1-results/transitions.gif&#34;/&gt;
&lt;/figure&gt;

&lt;h2 id=&#34;enemy-ai&#34;&gt;Enemy AI&lt;/h2&gt;
&lt;p&gt;While I wanted to add more enemy types to the game I simply ran out of time, so the game only features basic slimes.
Their AI is extremely simple too - just roam forward until there&amp;rsquo;s no more ground, a wall, or another entity, then turns around.&lt;/p&gt;
&lt;p&gt;Still, I had some trouble implementing this at first, as I tried to use the &lt;code&gt;world:check&lt;/code&gt; method to try and move the slime forward and below to check if there&amp;rsquo;s ground to traverse on.
But this didn&amp;rsquo;t work for some reason, so I opted for advanced bump API &lt;code&gt;world:queryRect&lt;/code&gt; and queried the rectangle below the slime instead.
It worked nicely, and perhaps I&amp;rsquo;ll use this to make a more robust AI in future games.&lt;/p&gt;
&lt;p&gt;I was toying for a little bit with flying enemies, but it&amp;rsquo;s hard to make them work right with the environment.
I didn&amp;rsquo;t want something like Medusa heads from Castlevania games, but that&amp;rsquo;s exactly what I got during testing.&lt;/p&gt;
&lt;p&gt;I also finally added the death mechanic, as previously the game lacked it completely.
Death doesn&amp;rsquo;t take away your score, as the game turned out to be hard, and there are not enough coins in the only level to get 100 of them and earn an extra life.&lt;/p&gt;
&lt;h2 id=&#34;level-design&#34;&gt;Level design&lt;/h2&gt;
&lt;p&gt;Probably shouldn&amp;rsquo;t have left this to the last possible moment, but designing interesting levels turned out to be a lot harder than I thought initially.
I think I&amp;rsquo;ve managed to at least do a somewhat challenging one, but you&amp;rsquo;re the judge here.
Probably most enemies and traps are cheap.&lt;/p&gt;
&lt;p&gt;As I mentioned previously, I&amp;rsquo;ve used the map editor to construct the level:&lt;/p&gt;
&lt;figure class=&#34;pixelart&#34;&gt;&lt;img src=&#34;https://andreyor.st/2023-07-30-game1-results/level.png&#34;/&gt;
&lt;/figure&gt;

&lt;p&gt;It&amp;rsquo;s a shame though that &lt;a href=&#34;https://www.mapeditor.org/&#34; target=&#34;_blank&#34;&gt;Tiled&lt;/a&gt;, a tilemap editor, doesn&amp;rsquo;t support TIC&amp;rsquo;s maps.
Well, there&amp;rsquo;s a converter but it didn&amp;rsquo;t work for me.
Map editor in TIC isn&amp;rsquo;t bad, but it isn&amp;rsquo;t as good as something like Tiled.
For example, Tiled has randomization from a selected set of tiles when you paint with them, which makes things like ground feel less repetitive.
In TIC I had to do it by hand, and I may have forgotten to do it somewhere in the game, so excuse me if you&amp;rsquo;ll see something like that.&lt;/p&gt;
&lt;p&gt;I used the bottom of the map to draw menus, backgrounds, transitions, and so on, as can be seen here:&lt;/p&gt;
&lt;figure class=&#34;pixelart&#34;&gt;&lt;img src=&#34;https://andreyor.st/2023-07-30-game1-results/map.png&#34;/&gt;
&lt;/figure&gt;

&lt;p&gt;My initial idea was to implement the first few levels like this, one below another, and then switch the memory bank, draw a new set of tiles, and make more levels.
But not only I ran out of time, I also ran out of ideas.
Maybe next time.&lt;/p&gt;
&lt;p&gt;Overall, I&amp;rsquo;d say making levels isn&amp;rsquo;t my least favorite thing so far but it&amp;rsquo;s close.
I&amp;rsquo;ve played in a fair bit of games with level editors and even published some online, but it never grew on me.
I guess I&amp;rsquo;m not a level designer at heart.&lt;/p&gt;
&lt;h2 id=&#34;music-and-sfx&#34;&gt;Music and SFX&lt;/h2&gt;
&lt;p&gt;This was probably the hardest part.
I can&amp;rsquo;t compose music and TIC&amp;rsquo;s tracker is alien to me. Sound design is a bit easier, but I don&amp;rsquo;t know a lot of tricks, so sounds in this game are far and few between.
Still was worth a shot.&lt;/p&gt;
&lt;figure class=&#34;pixelart&#34;&gt;&lt;img src=&#34;https://andreyor.st/2023-07-30-game1-results/coin-sound.png&#34;/&gt;
&lt;/figure&gt;

&lt;h2 id=&#34;up-next&#34;&gt;Up next&lt;/h2&gt;
&lt;p&gt;The next game in the challenge is a roguelike.
This kind of game poses different problems, like pathfinding, more complex enemy AI in general, randomization of levels, items, etc.
I kinda feel exhausted right now but I&amp;rsquo;ll still try not to repeat the mistake I made and not procrastinate too much in the first weeks.
Though the next month is packed with various life-related events, much more than this one, so we&amp;rsquo;ll see.&lt;/p&gt;
&lt;p&gt;As for this game, here&amp;rsquo;s the cartridge if you want to play it yourself:&lt;/p&gt;
&lt;figure class=&#34;pixelart&#34;&gt;&lt;img src=&#34;https://andreyor.st/2023-07-30-game1-results/game1.png&#34;/&gt;
&lt;/figure&gt;

&lt;p&gt;Though, because the original code requires a more recent version of Fennel than the one TIC provides, the code in the cart above was AOT compiled to Lua and isn&amp;rsquo;t very readable.
Also note that the last stable release of TIC still contains the &lt;a href=&#34;https://github.com/nesbox/TIC-80/issues/1904&#34; target=&#34;_blank&#34;&gt;#1904&lt;/a&gt; bug, which means that my game will run ~10 times slower than it should.
So you either need to play the game on the itch.io page, as it features a more recent version, build a standalone version yourself, or use an older release with the Lua version above.
You can read the original source code at the itch.io page linked above, or in the &lt;a href=&#34;https://gitlab.com/andreyorst/game1/-/blob/main/cart.fnl&#34; target=&#34;_blank&#34;&gt;game repository&lt;/a&gt;.&lt;/p&gt;
&lt;div class=&#34;comment-link&#34;&gt; &lt;a href=&#34;mailto:%61%6e%64%72%65%79%6f%72%73%74%2b%62%6c%6f%67%40%67%6d%61%69%6c%2e%63%6f%6d?subject=Comment: Game1 - Results&#34; target=&#34;_blank&#34; rel=&#34;noopener noreferrer&#34;&gt;Comment via email&lt;/a&gt;&lt;/div&gt;</description>
      <pubDate>Sun, 30 Jul 2023 15:29:00 +0300</pubDate>
    </item><item>
      <title>Game1 W4/4 progress</title>
      <link>https://andreyor.st/posts/2023-07-26-game1-w44-progress/</link>
      <guid>https://andreyor.st/posts/2023-07-26-game1-w44-progress/</guid>
      <description>&lt;p&gt;This post is midway through the last week I have to work on the game in a platforming genre.
And it&amp;rsquo;s a bit of a shame because currently, I&amp;rsquo;m having a blast - now that the physics and camera are in place the game already feels like a playable thing.
So I decided to boost this feel and make some graphics so the world won&amp;rsquo;t look too boring to traverse.&lt;/p&gt;
&lt;p&gt;I haven&amp;rsquo;t decided on what the world be like yet, but I&amp;rsquo;m running out of time, and also, I&amp;rsquo;m not that experienced in pixel art.
A few posts ago I wrote how I tried to do pixel art on the phone by using a stylus, and it worked pretty well for me.
So I took out the app again and started thinking about how would I use the colors that are still available to me.&lt;/p&gt;
&lt;p&gt;I&amp;rsquo;ve briefly considered changing the palette too.
The default TIC-80 palette is a bit weird for my taste, and it doesn&amp;rsquo;t include some colors that I&amp;rsquo;d like to have, e.g. brown.
I don&amp;rsquo;t know how, but every time I see some pixel art that uses this particular palette I swear it has brown, but upon closer inspection it&amp;rsquo;s an illusion caused by interplay with the other colors.
Unable to find a new palette that works, and also that doesn&amp;rsquo;t make every other sprite I drew up to this point use some weird colors, I gave up for now.
Maybe I&amp;rsquo;ll just look at what colors I don&amp;rsquo;t use at all or use too infrequently and replace them with the colors I need for my purposes.&lt;/p&gt;
&lt;p&gt;For now, I drew some grass on stone type of terrain (again, no brown for dirt!), and some construction site-type red bars:&lt;/p&gt;
&lt;figure class=&#34;pixelart&#34;&gt;&lt;img src=&#34;https://andreyor.st/2023-07-26-game1-w44-progress/tiles.png&#34;/&gt;
&lt;/figure&gt;

&lt;p&gt;Note, these are tiles, e.g. what goes on the map, but there are some things that are not part of the map, for example, the coin, or the slime enemy.
I&amp;rsquo;ll explain why they&amp;rsquo;re here shortly.&lt;/p&gt;
&lt;p&gt;And here are the sprites:&lt;/p&gt;
&lt;figure class=&#34;pixelart&#34;&gt;&lt;img src=&#34;https://andreyor.st/2023-07-26-game1-w44-progress/sprites.png&#34;/&gt;
&lt;/figure&gt;

&lt;p&gt;As you can see, I&amp;rsquo;m storing these sprites mostly in rows, and tall sprites, like the player character look naturally in the editor.
Except for the ones in the third and fourth rows first half - these are horizontal sprites, but I still store them this way, because of how I do animations.
This reminds me that I haven&amp;rsquo;t explained anything regarding how my game works!
I guess I was too busy implementing it and didn&amp;rsquo;t have any spare time to write a proper post.&lt;/p&gt;
&lt;p&gt;So the animations are implemented in terms of sprites, obviously, but the way I set up them is a bit weird.
And I know, I definitively could save some space and re-use some sprites, because clearly there are doubles or even triples of the same sprite.
But it was easier this way, so I did it anyway.
I have this function, called &lt;code&gt;sprites&lt;/code&gt; that takes an id of the first sprite, the last sprite, and an optional &lt;code&gt;loop?&lt;/code&gt; parameter:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-fennel&#34; data-lang=&#34;fennel&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;(&lt;span style=&#34;font-weight:bold;font-style:italic&#34;&gt;fn &lt;/span&gt;&lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;sprites&lt;/span&gt; [&lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;from&lt;/span&gt; &lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;to&lt;/span&gt; &lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;loop?&lt;/span&gt;]
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  (&lt;span style=&#34;font-weight:bold&#34;&gt;let &lt;/span&gt;[&lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;sprites&lt;/span&gt; (&lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;fcollect&lt;/span&gt; [&lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;id&lt;/span&gt; &lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;from&lt;/span&gt; (&lt;span style=&#34;font-weight:bold&#34;&gt;or &lt;/span&gt;&lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;to&lt;/span&gt; &lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;from&lt;/span&gt;)] &lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;id&lt;/span&gt;)]
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    (&lt;span style=&#34;font-weight:bold&#34;&gt;doto &lt;/span&gt;&lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;sprites&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      (&lt;span style=&#34;font-weight:bold&#34;&gt;tset &lt;/span&gt;&lt;span style=&#34;color:#666;font-style:italic&#34;&gt;:n&lt;/span&gt; (&lt;span style=&#34;font-weight:bold&#34;&gt;length &lt;/span&gt;&lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;sprites&lt;/span&gt;))
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      (&lt;span style=&#34;font-weight:bold&#34;&gt;tset &lt;/span&gt;&lt;span style=&#34;color:#666;font-style:italic&#34;&gt;:loop?&lt;/span&gt; &lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;loop?&lt;/span&gt;))))
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;The result of this function is a table, that simply holds a range of IDs for all the sprites in the animation.
Does the &lt;code&gt;n&lt;/code&gt; key hold the total count of sprites, and &lt;code&gt;loop?&lt;/code&gt;, well, we&amp;rsquo;ll get to it in a moment.&lt;/p&gt;
&lt;p&gt;The other component of animation is how I choose when to change the frame.
I&amp;rsquo;m sure there&amp;rsquo;s a different, and probably more robust way of doing this, but what I&amp;rsquo;ve settled on is this function:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-fennel&#34; data-lang=&#34;fennel&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;(&lt;span style=&#34;font-weight:bold;font-style:italic&#34;&gt;fn &lt;/span&gt;&lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;next-frame&lt;/span&gt; [&lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;obj&lt;/span&gt; &lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;dt&lt;/span&gt;]
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  (&lt;span style=&#34;font-weight:bold&#34;&gt;let &lt;/span&gt;[&lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;dt&lt;/span&gt; (&lt;span style=&#34;font-weight:bold&#34;&gt;+ &lt;/span&gt;&lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;dt&lt;/span&gt; (&lt;span style=&#34;font-weight:bold&#34;&gt;or &lt;/span&gt;&lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;obj.last-draw-time&lt;/span&gt; 0))]
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    (&lt;span style=&#34;font-weight:bold&#34;&gt;if &lt;/span&gt;(&lt;span style=&#34;font-weight:bold&#34;&gt;&amp;gt;= &lt;/span&gt;&lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;dt&lt;/span&gt; 60)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        (&lt;span style=&#34;font-weight:bold&#34;&gt;let &lt;/span&gt;[&lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;sprites&lt;/span&gt; (&lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;obj&lt;/span&gt;&lt;span style=&#34;color:#666;font-style:italic&#34;&gt;:sprites&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;              &lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;n&lt;/span&gt; (&lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;m/floor&lt;/span&gt; (&lt;span style=&#34;font-weight:bold&#34;&gt;/ &lt;/span&gt;&lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;dt&lt;/span&gt; 60))]
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;          (&lt;span style=&#34;font-weight:bold&#34;&gt;set &lt;/span&gt;&lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;obj.last-draw-time&lt;/span&gt; 0)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;          (&lt;span style=&#34;font-weight:bold&#34;&gt;if &lt;/span&gt;(&lt;span style=&#34;font-weight:bold&#34;&gt;= &lt;/span&gt;&lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;obj.frame&lt;/span&gt; &lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;sprites.n&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;              (&lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;case&lt;/span&gt; &lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;sprites.loop?&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                &lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;n&lt;/span&gt; (&lt;span style=&#34;font-weight:bold&#34;&gt;if &lt;/span&gt;(&lt;span style=&#34;font-weight:bold&#34;&gt;= &lt;/span&gt;&lt;span style=&#34;color:#666;font-style:italic&#34;&gt;:number&lt;/span&gt; (&lt;span style=&#34;font-weight:bold;font-style:italic&#34;&gt;type &lt;/span&gt;&lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;n&lt;/span&gt;))
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                      (&lt;span style=&#34;font-weight:bold&#34;&gt;set &lt;/span&gt;&lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;obj.frame&lt;/span&gt; &lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;n&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                      (&lt;span style=&#34;font-weight:bold&#34;&gt;set &lt;/span&gt;&lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;obj.frame&lt;/span&gt; 1)))
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;              (&lt;span style=&#34;font-weight:bold&#34;&gt;set &lt;/span&gt;&lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;obj.frame&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                (&lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;clamp&lt;/span&gt; 1 (&lt;span style=&#34;font-weight:bold&#34;&gt;+ &lt;/span&gt;&lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;n&lt;/span&gt; &lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;obj.frame&lt;/span&gt;) (&lt;span style=&#34;font-weight:bold&#34;&gt;length &lt;/span&gt;&lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;sprites&lt;/span&gt;)))))
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        (&lt;span style=&#34;font-weight:bold&#34;&gt;set &lt;/span&gt;&lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;obj.last-draw-time&lt;/span&gt; &lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;dt&lt;/span&gt;))))
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;As you can see, this function receives an object &lt;code&gt;obj&lt;/code&gt; and a delta time &lt;code&gt;dt&lt;/code&gt;.
The object stores its own draw time, and when the sum of &lt;code&gt;dt&lt;/code&gt; and this time is greater than &lt;code&gt;60&lt;/code&gt; I change the frame.
The &lt;code&gt;loop?&lt;/code&gt; field is used in a special way - when the next frame exceeds the total sprite count, we check if the animation should loop.
If the field is &lt;code&gt;true&lt;/code&gt; the animation loops from the first frame.
However, if the field is a number, we restart the animation from that number.
This way the animation can have a set of frames that act as preparation or transition, and then loop only the necessary part.&lt;/p&gt;
&lt;p&gt;Here&amp;rsquo;s how I draw coins:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-fennel&#34; data-lang=&#34;fennel&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;(&lt;span style=&#34;font-weight:bold;font-style:italic&#34;&gt;local &lt;/span&gt;&lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;coin-sprites&lt;/span&gt; (&lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;sprites&lt;/span&gt; 330 335 &lt;span style=&#34;color:#666;font-style:italic&#34;&gt;:loop&lt;/span&gt;))
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;(&lt;span style=&#34;font-weight:bold;font-style:italic&#34;&gt;local &lt;/span&gt;&lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;coin-collect-sprites&lt;/span&gt; (&lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;sprites&lt;/span&gt; 346 351))
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;(&lt;span style=&#34;font-weight:bold;font-style:italic&#34;&gt;fn &lt;/span&gt;&lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;draw-coin&lt;/span&gt; [{&lt;span style=&#34;font-weight:bold&#34;&gt;: &lt;/span&gt;&lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;x&lt;/span&gt; &lt;span style=&#34;font-weight:bold&#34;&gt;: &lt;/span&gt;&lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;y&lt;/span&gt; &lt;span style=&#34;font-weight:bold&#34;&gt;: &lt;/span&gt;&lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;frame&lt;/span&gt; &amp;amp;&lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;as&lt;/span&gt; &lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;self&lt;/span&gt;} {&lt;span style=&#34;color:#666;font-style:italic&#34;&gt;:x&lt;/span&gt; &lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;cam-x&lt;/span&gt; &lt;span style=&#34;color:#666;font-style:italic&#34;&gt;:y&lt;/span&gt; &lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;cam-y&lt;/span&gt;}]
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  (&lt;span style=&#34;font-weight:bold&#34;&gt;match &lt;/span&gt;(&lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;self&lt;/span&gt;&lt;span style=&#34;color:#666;font-style:italic&#34;&gt;:sprites&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;coin-sprites&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    (&lt;span style=&#34;font-weight:bold&#34;&gt;let &lt;/span&gt;[&lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;s&lt;/span&gt; (&lt;span style=&#34;font-weight:bold&#34;&gt;or &lt;/span&gt;(&lt;span style=&#34;font-weight:bold&#34;&gt;. &lt;/span&gt;&lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;coin-sprites&lt;/span&gt; &lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;frame&lt;/span&gt;) 511)]
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      (&lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;spr&lt;/span&gt; &lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;s&lt;/span&gt; (&lt;span style=&#34;font-weight:bold&#34;&gt;+ &lt;/span&gt;&lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;cam-x&lt;/span&gt; -120 &lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;x&lt;/span&gt;) (&lt;span style=&#34;font-weight:bold&#34;&gt;+ &lt;/span&gt;&lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;cam-y&lt;/span&gt; -64 &lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;y&lt;/span&gt;) 0 1)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      (&lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;next-frame&lt;/span&gt; &lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;self&lt;/span&gt; (&lt;span style=&#34;font-weight:bold&#34;&gt;/ &lt;/span&gt;&lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;dt&lt;/span&gt; 2)))
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;coin-collect-sprites&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    (&lt;span style=&#34;font-weight:bold&#34;&gt;let &lt;/span&gt;[&lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;s&lt;/span&gt; (&lt;span style=&#34;font-weight:bold&#34;&gt;or &lt;/span&gt;(&lt;span style=&#34;font-weight:bold&#34;&gt;. &lt;/span&gt;&lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;coin-collect-sprites&lt;/span&gt; &lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;frame&lt;/span&gt;) 511)]
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      (&lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;spr&lt;/span&gt; &lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;s&lt;/span&gt; (&lt;span style=&#34;font-weight:bold&#34;&gt;+ &lt;/span&gt;&lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;cam-x&lt;/span&gt; -120 &lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;x&lt;/span&gt;) (&lt;span style=&#34;font-weight:bold&#34;&gt;+ &lt;/span&gt;&lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;cam-y&lt;/span&gt; -64 &lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;y&lt;/span&gt;) 0 1)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      (&lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;next-frame&lt;/span&gt; &lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;self&lt;/span&gt; &lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;dt&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      (&lt;span style=&#34;font-weight:bold&#34;&gt;when &lt;/span&gt;(&lt;span style=&#34;font-weight:bold&#34;&gt;= &lt;/span&gt;&lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;frame&lt;/span&gt; &lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;collect-sprites.n&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        (&lt;span style=&#34;font-weight:bold&#34;&gt;tset &lt;/span&gt;&lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;coins&lt;/span&gt; &lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;self&lt;/span&gt; &lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;nil&lt;/span&gt;)))))
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;There are some wonky numbers in here to adjust to the center of the screen and camera position in the world, but the code should be straightforward.
The &lt;code&gt;(self:sprites)&lt;/code&gt; call simply returns the object&amp;rsquo;s current set of sprites, which I change in other functions depending on the situation.
E.g. if the coin was collected, the object&amp;rsquo;s sprite is changed to the &lt;code&gt;coin-collect-sprites&lt;/code&gt; which doesn&amp;rsquo;t loop, and the last sprite of that animation is invisible.
The coin itself is deleted from the world once the last &lt;code&gt;frame&lt;/code&gt; of the animation was played out.&lt;/p&gt;
&lt;p&gt;&lt;video autoplay loop muted class=&#34;pixelart&#34;&gt;&lt;source src=&#34;https://andreyor.st/2023-07-26-game1-w44-progress/coin-animation.mp4&#34; type=&#34;video/mp4&#34;&gt;
Your browser does not support the video tag.&lt;/video&gt;&lt;/p&gt;
&lt;p&gt;This is the basis for all animation in the game.
Each object has it&amp;rsquo;s &lt;code&gt;:draw&lt;/code&gt; method, and each such method calls to &lt;code&gt;next-frame&lt;/code&gt; to understand what to display next time.&lt;/p&gt;
&lt;p&gt;Interaction with entities is done similarly - each entity has an &lt;code&gt;:interact&lt;/code&gt; method that decides what to do when a collision is detected.
For example, for the slime enemy, the method accepts the &lt;code&gt;y&lt;/code&gt; normal of the collision, and if it is not negative 1, which means that the collision happened from the side or from below, the player is damaged.
Otherwise, the slime sprite set is replaced with the stomp animation, and the object is deleted from the world:&lt;/p&gt;
&lt;p&gt;&lt;video autoplay loop muted class=&#34;pixelart&#34;&gt;&lt;source src=&#34;https://andreyor.st/2023-07-26-game1-w44-progress/slime.mp4&#34; type=&#34;video/mp4&#34;&gt;
Your browser does not support the video tag.&lt;/video&gt;&lt;/p&gt;
&lt;p&gt;By the way, I haven&amp;rsquo;t talked about how I structure the world yet.
You may know, that TIC has an inbuilt map editor:&lt;/p&gt;
&lt;figure class=&#34;pixelart&#34;&gt;&lt;img src=&#34;https://andreyor.st/2023-07-26-game1-w44-progress/map-editor.png&#34;/&gt;
&lt;/figure&gt;

&lt;p&gt;This is basically a level designer for me because I&amp;rsquo;ve implemented a small function that upon loading the cart walks through each tile on the map and populates the world based on it.
I still use &lt;code&gt;map&lt;/code&gt; for drawing the level, but before that, all entity tiles are removed from the map, like in the image above coins and slimes, and the player&amp;rsquo;s head, which acts as a spawn point.
This way I can simply put objects in the map editor, and re-run the game, getting an updated world, complete with collectibles, power-ups, enemies, etc.:&lt;/p&gt;
&lt;p&gt;&lt;video autoplay loop muted class=&#34;pixelart&#34;&gt;&lt;source src=&#34;https://andreyor.st/2023-07-26-game1-w44-progress/level.webm&#34; type=&#34;video/webm&#34;&gt;
Your browser does not support the video tag.&lt;/video&gt;&lt;/p&gt;
&lt;p&gt;Here I&amp;rsquo;m obviously testing things out, but the game feels complete enough to actually start designing some actual levels.
I&amp;rsquo;m running out of time though, so perhaps I&amp;rsquo;ll just make a single course to complete, and that&amp;rsquo;s it.
But it&amp;rsquo;s already more than I&amp;rsquo;ve anticipated at the beginning!&lt;/p&gt;
&lt;p&gt;I&amp;rsquo;ve also started working on the menu, but there&amp;rsquo;s nothing to show yet.&lt;/p&gt;
&lt;p&gt;From now, I&amp;rsquo;ll probably stop doing any in-game stuff, and concentrate on the level itself and I also need some audio to play when the player collects coins, takes damage, jumps, and so on.
Maybe I&amp;rsquo;ll even include some music if I&amp;rsquo;ll manage to compose something not too annoying to listen to.&lt;/p&gt;
&lt;div class=&#34;comment-link&#34;&gt; &lt;a href=&#34;mailto:%61%6e%64%72%65%79%6f%72%73%74%2b%62%6c%6f%67%40%67%6d%61%69%6c%2e%63%6f%6d?subject=Comment: Game1 W4/4 progress&#34; target=&#34;_blank&#34; rel=&#34;noopener noreferrer&#34;&gt;Comment via email&lt;/a&gt;&lt;/div&gt;</description>
      <pubDate>Wed, 26 Jul 2023 20:09:00 +0300</pubDate>
    </item><item>
      <title>Game1 W3/4 (again)</title>
      <link>https://andreyor.st/posts/2023-07-22-game1-w34-again/</link>
      <guid>https://andreyor.st/posts/2023-07-22-game1-w34-again/</guid>
      <description>&lt;p&gt;Who knew that writing a post about how I&amp;rsquo;ve procrastinated for two whole weeks instead of following my own challenge would be so motivating?
Immediately after I posted the previous post on Monday, I regained interest and started working on physics integration.
Well, making a platformer without experience turned out to be harder than I thought, though I got stuck on things that are not platformer specific.&lt;/p&gt;
&lt;p&gt;For the first half of the week, I&amp;rsquo;ve been trying to implement collision resolution myself.
This&amp;hellip; didn&amp;rsquo;t go well, as can be seen here:&lt;/p&gt;
&lt;figure class=&#34;pixelart&#34;&gt;&lt;img src=&#34;https://andreyor.st/2023-07-22-game1-w34-again/custom-collisions.gif&#34;
         alt=&#34;Figure 1: The green rectangles depict the area in which I check for possible collisions, and red rectangles mark tiles that collided with the player&#34;/&gt;&lt;figcaption&gt;
            &lt;p&gt;&lt;span class=&#34;figure-number&#34;&gt;Figure 1: &lt;/span&gt;The green rectangles depict the area in which I check for possible collisions, and red rectangles mark tiles that collided with the player&lt;/p&gt;
        &lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p&gt;I&amp;rsquo;ve read some tutorials on &lt;a href=&#34;https://github.com/nesbox/TIC-80/wiki/tutorials&#34; target=&#34;_blank&#34;&gt;TIC&amp;rsquo;s wiki&lt;/a&gt;, mainly &lt;a href=&#34;https://medium.com/@btco_code/writing-a-platformer-for-the-tic-80-virtual-console-6fa737abe476&#34; target=&#34;_blank&#34;&gt;this&lt;/a&gt; one.
It mentions how to track collisions by checking nearby squarest cells on the map grid.
That was exactly my idea, but it didn&amp;rsquo;t work well, as was shown in the GIF above.&lt;/p&gt;
&lt;p&gt;I suspect the main reason for that is that the player object moves at irregular intervals because the distance is determined by velocity, and not constant as in the tutorial.
Thus, the object often overshoots or undershoots the wall and the floor, which led to the player stuck in walls, or going completely through them.
It reminded me of problems I had with my &lt;a href=&#34;https://andreyorst.itch.io/brick-game-with-a-twist&#34; target=&#34;_blank&#34;&gt;previous game&lt;/a&gt;, where the ball often got through the platform and bricks, so after failing to do physics again I decided to use a library.&lt;/p&gt;
&lt;p&gt;Using a library in a TIC game means that the library has to be included in the game cart because there&amp;rsquo;s no other way of shipping games for this platform - a cart is a self-contained program without external dependencies.
TIC&amp;rsquo;s carts also have limited capacity so the amount of code I can include is also limited.
Fortunately, there&amp;rsquo;s a small enough library called &lt;a href=&#34;https://github.com/kikito/bump.lua&#34; target=&#34;_blank&#34;&gt;bump.lua&lt;/a&gt; that does AABB collision detection, which is exactly what I need for this type of game.
TIC games are tile-based, so axis-aligned rectangles are exactly what I need to handle.
With some trickery, it is even possible to implement slopes by using special kinds of boxes that define a slope offset based on the X coordinate.&lt;/p&gt;
&lt;p&gt;There was a problem though.
&lt;code&gt;bump.lua&lt;/code&gt; is a Lua library, and while TIC supports Lua, I&amp;rsquo;m doing this game in Fennel.
Usually, it&amp;rsquo;s not a problem, because projects written in Fennel can use Lua libraries without any issues, and technically I could have used &lt;code&gt;bump.lua&lt;/code&gt; as a library from TIC-80 too.
But, as I mentioned, TIC carts must be self-contained, and thus can&amp;rsquo;t load an external library.
So I had to decide - either I port &lt;code&gt;bump.lua&lt;/code&gt; to Fennel, or compile my Fennel code to Lua and continue the development this way.
I also considered re-implementing a smaller subset of &lt;code&gt;bump.lua&lt;/code&gt; specifically for using it in TIC with map tiles, which would probably make the library smaller and easier to handle, but I decided that I want to actually see some results of my work at the end of this month.&lt;/p&gt;
&lt;p&gt;There was another compelling way of handling this situation though.
I could have used Fennel&amp;rsquo;s &lt;code&gt;-require-as-include&lt;/code&gt; compile flag to, well, include &lt;code&gt;bump.lua&lt;/code&gt; as an embedded dependency, and after the development is finished compile the cart to Lua right before shipping.
This would be the easiest way of doing things, as it would mean that I can do a completely normal development and use any libraries I want, but the downside is that the cart will be a hot mess of obfuscated Lua code, and I wanted the cart to actually contain something readable.
Maybe I&amp;rsquo;ll try this in the future, who knows?&lt;/p&gt;
&lt;p&gt;So, after some consideration of continuing in Lua and just copying &lt;code&gt;bump.fnl&lt;/code&gt; code into the cart, I decided to translate &lt;code&gt;bump.lua&lt;/code&gt; to Fennel.
As much as I like Lua, Fennel is more pleasant to work with.
It wasn&amp;rsquo;t that hard to do, given that there&amp;rsquo;s an &lt;a href=&#34;https://fennel-lang.org/see&#34; target=&#34;_blank&#34;&gt;excellent Lua-to-Fennel decompiler&lt;/a&gt;, that did most of the job.
It does generate weird code here and there, calling &lt;code&gt;set-forcibly!&lt;/code&gt; every now and then, and inserting &lt;code&gt;(lua &amp;quot;return &amp;quot;)&lt;/code&gt;  in places where Lua code had a sole &lt;code&gt;return&lt;/code&gt;, or doing things like this:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-lua&#34; data-lang=&#34;lua&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#888;font-style:italic&#34;&gt;-- original Lua code&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;font-weight:bold;font-style:italic&#34;&gt;local&lt;/span&gt; &lt;span style=&#34;font-weight:bold&#34;&gt;function&lt;/span&gt; &lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;sortByTiAndDistance&lt;/span&gt;(a,b)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;font-weight:bold&#34;&gt;if&lt;/span&gt; a.ti == b.ti &lt;span style=&#34;font-weight:bold&#34;&gt;then&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;font-weight:bold;font-style:italic&#34;&gt;local&lt;/span&gt; ir, ar, br = a.itemRect, a.otherRect, b.otherRect
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;font-weight:bold;font-style:italic&#34;&gt;local&lt;/span&gt; ad = rect_getSquareDistance(ir.x,ir.y,ir.w,ir.h, ar.x,ar.y,ar.w,ar.h)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;font-weight:bold;font-style:italic&#34;&gt;local&lt;/span&gt; bd = rect_getSquareDistance(ir.x,ir.y,ir.w,ir.h, br.x,br.y,br.w,br.h)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;font-weight:bold&#34;&gt;return&lt;/span&gt; ad &amp;lt; bd
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;font-weight:bold&#34;&gt;end&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;font-weight:bold&#34;&gt;return&lt;/span&gt; a.ti &amp;lt; b.ti
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;font-weight:bold&#34;&gt;end&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-fennel&#34; data-lang=&#34;fennel&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#888;font-style:italic&#34;&gt;;; Lua decompiler result&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;(&lt;span style=&#34;font-weight:bold;font-style:italic&#34;&gt;fn &lt;/span&gt;&lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;sort-by-ti-and-distance&lt;/span&gt; [&lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;a&lt;/span&gt; &lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;b&lt;/span&gt;]
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  (&lt;span style=&#34;font-weight:bold&#34;&gt;when &lt;/span&gt;(&lt;span style=&#34;font-weight:bold&#34;&gt;= &lt;/span&gt;&lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;a.ti&lt;/span&gt; &lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;b.ti&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    (&lt;span style=&#34;font-weight:bold;font-style:italic&#34;&gt;local &lt;/span&gt;(&lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;ir&lt;/span&gt; &lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;ar&lt;/span&gt; &lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;br&lt;/span&gt;) (&lt;span style=&#34;font-weight:bold&#34;&gt;values &lt;/span&gt;&lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;a.itemRect&lt;/span&gt; &lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;a.otherRect&lt;/span&gt; &lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;b.otherRect&lt;/span&gt;))
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    (&lt;span style=&#34;font-weight:bold;font-style:italic&#34;&gt;local &lt;/span&gt;&lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;ad&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;           (&lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;rect-get-square-distance&lt;/span&gt; &lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;ir.x&lt;/span&gt; &lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;ir.y&lt;/span&gt; &lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;ir.w&lt;/span&gt; &lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;ir.h&lt;/span&gt; &lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;ar.x&lt;/span&gt; &lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;ar.y&lt;/span&gt; &lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;ar.w&lt;/span&gt; &lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;ar.h&lt;/span&gt;))
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    (&lt;span style=&#34;font-weight:bold;font-style:italic&#34;&gt;local &lt;/span&gt;&lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;bd&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;           (&lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;rect-get-square-distance&lt;/span&gt; &lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;ir.x&lt;/span&gt; &lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;ir.y&lt;/span&gt; &lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;ir.w&lt;/span&gt; &lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;ir.h&lt;/span&gt; &lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;br.x&lt;/span&gt; &lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;br.y&lt;/span&gt; &lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;br.w&lt;/span&gt; &lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;br.h&lt;/span&gt;))
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    (&lt;span style=&#34;font-weight:bold&#34;&gt;let &lt;/span&gt;[&lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;___antifnl_rtn_1___&lt;/span&gt; (&lt;span style=&#34;font-weight:bold&#34;&gt;&amp;lt; &lt;/span&gt;&lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;ad&lt;/span&gt; &lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;bd&lt;/span&gt;)] (&lt;span style=&#34;font-weight:bold&#34;&gt;lua &lt;/span&gt;&lt;span style=&#34;color:#666;font-style:italic&#34;&gt;&amp;#34;return ___antifnl_rtn_1___&amp;#34;&lt;/span&gt;)))
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  (&lt;span style=&#34;font-weight:bold&#34;&gt;&amp;lt; &lt;/span&gt;&lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;a.ti&lt;/span&gt; &lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;b.ti&lt;/span&gt;))
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#888;font-style:italic&#34;&gt;;; cleaned version&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;(&lt;span style=&#34;font-weight:bold;font-style:italic&#34;&gt;fn &lt;/span&gt;&lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;sort-by-ti-and-distance&lt;/span&gt; [&lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;a&lt;/span&gt; &lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;b&lt;/span&gt;]
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  (&lt;span style=&#34;font-weight:bold&#34;&gt;if &lt;/span&gt;(&lt;span style=&#34;font-weight:bold&#34;&gt;= &lt;/span&gt;&lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;a.ti&lt;/span&gt; &lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;b.ti&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      (&lt;span style=&#34;font-weight:bold&#34;&gt;let &lt;/span&gt;[&lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;ir&lt;/span&gt; &lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;a.itemRect&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            &lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;ar&lt;/span&gt; &lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;a.otherRect&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            &lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;br&lt;/span&gt; &lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;b.otherRect&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            &lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;ad&lt;/span&gt; (&lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;rect-get-square-distance&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                &lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;ir.x&lt;/span&gt; &lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;ir.y&lt;/span&gt; &lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;ir.w&lt;/span&gt; &lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;ir.h&lt;/span&gt; &lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;ar.x&lt;/span&gt; &lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;ar.y&lt;/span&gt; &lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;ar.w&lt;/span&gt; &lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;ar.h&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            &lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;bd&lt;/span&gt; (&lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;rect-get-square-distance&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                &lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;ir.x&lt;/span&gt; &lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;ir.y&lt;/span&gt; &lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;ir.w&lt;/span&gt; &lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;ir.h&lt;/span&gt; &lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;br.x&lt;/span&gt; &lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;br.y&lt;/span&gt; &lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;br.w&lt;/span&gt; &lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;br.h&lt;/span&gt;)]
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        (&lt;span style=&#34;font-weight:bold&#34;&gt;&amp;lt; &lt;/span&gt;&lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;ad&lt;/span&gt; &lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;bd&lt;/span&gt;))
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      (&lt;span style=&#34;font-weight:bold&#34;&gt;&amp;lt; &lt;/span&gt;&lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;a.ti&lt;/span&gt; &lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;b.ti&lt;/span&gt;)))
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;div class=&#34;src-block-caption&#34;&gt;
  &lt;span class=&#34;src-block-number&#34;&gt;Code Snippet 1:&lt;/span&gt;
  e.g. here, the decompiler transformed a single-branch &lt;code&gt;if&lt;/code&gt; into &lt;code&gt;when&lt;/code&gt; and generated an explicit return, but a smarter decompiler could transform this into two branched &lt;code&gt;if&lt;/code&gt; without any early returns. Conceptually these two variants are the same, except one is more like the guard-return style and another is more like actual lisp-style.
&lt;/div&gt;
&lt;p&gt;So I took some extra time to clean all &lt;code&gt;set-forcibly!&lt;/code&gt; calls, restored the original comments which were lost during translation, and removed all instances of explicit early returns, by converting the code to use branches instead.
After compiling the library back to Lua, I tested it with some demos listed in the library&amp;rsquo;s readme and it worked fine, so I hope I didn&amp;rsquo;t introduce any new bugs during the cleanup state.
The next thing was to integrate it into the game.&lt;/p&gt;
&lt;p&gt;It wasn&amp;rsquo;t that hard to do, as I just copied the whole library code into the cart, but I was worried if it will be able to handle &lt;em&gt;lots&lt;/em&gt; of squares.
My idea was to iterate over every map cell, check if it is solid or not, and if so add it to the world.
Thus, each non-empty tile on the map exists in the world and acts as a collideable object.
As a result, I can use the map editor basically as a level editor, as adding a tile basically is like adding a world object.&lt;/p&gt;
&lt;p&gt;The resulting performance seems to be fine on my PC, though I&amp;rsquo;m unsure about the browser which I&amp;rsquo;ll target to make the game playable on my itch.io web page.
I&amp;rsquo;m still exploring the idea of using special tile flags to make rooms longer than they seem on the map, as described in the platformer tutorial that I&amp;rsquo;ve mentioned.
But this probably requires special handling when it comes to integrating it with the bump library.
For now, the result (with some debug info) looks like this:&lt;/p&gt;
&lt;figure class=&#34;pixelart&#34;&gt;&lt;img src=&#34;https://andreyor.st/2023-07-22-game1-w34-again/bump.gif&#34;
         alt=&#34;Figure 2: bump.lua in TIC-80 with some debug visualisations of collisions&#34;/&gt;&lt;figcaption&gt;
            &lt;p&gt;&lt;span class=&#34;figure-number&#34;&gt;Figure 2: &lt;/span&gt;bump.lua in TIC-80 with some debug visualisations of collisions&lt;/p&gt;
        &lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p&gt;The next thing to do was the camera.
Now, since each object in the world has the same coordinate as the map, the only thing I need to do is implement the translation of world coordinates to screen coordinates based on player position with some slight interpolation.
Fortunately enough, there&amp;rsquo;s a handy &lt;a href=&#34;https://github.com/nesbox/TIC-80/wiki/Camera-tutorial&#34; target=&#34;_blank&#34;&gt;tutorial&lt;/a&gt; on camera movement, and while I&amp;rsquo;d like to try and implement this myself, I decided to rely on external source of wisdom.&lt;/p&gt;
&lt;p&gt;Unfortunately, for some weird reason, the camera described in this tutorial worked differently for me.
For some reason, the player sprite was drawn in the top left corner of the screen, and the camera was also positioned there.
I mean, it moves correctly but is unplayable in this state.
After I&amp;rsquo;ve &lt;em&gt;experimented&lt;/em&gt; with some random numbers I&amp;rsquo;ve managed to get it to work:&lt;/p&gt;
&lt;figure class=&#34;pixelart&#34;&gt;&lt;img src=&#34;https://andreyor.st/2023-07-22-game1-w34-again/camera.gif&#34;
         alt=&#34;Figure 3: The camera moves with a slight delay to add a bit more dynamic feel, but I didn&amp;amp;rsquo;t go for a more proper camera implementation that tries to show more in the direction of movement yet. Also, what&amp;amp;rsquo;s up with these GIFs generated by TIC, their size is around 10MB each, so I had to heavily compress them myself, yet they look entirely the same afterwards.&#34;/&gt;&lt;figcaption&gt;
            &lt;p&gt;&lt;span class=&#34;figure-number&#34;&gt;Figure 3: &lt;/span&gt;The camera moves with a slight delay to add a bit more dynamic feel, but I didn&amp;rsquo;t go for a more proper camera implementation that tries to show more in the direction of movement yet. Also, what&amp;rsquo;s up with these GIFs generated by TIC, their size is around 10MB each, so I had to heavily compress them myself, yet they look entirely the same afterwards.&lt;/p&gt;
        &lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p&gt;With that in place, now I need to design some levels and add more graphics, and sounds.
As well, as implement actual environment interactions, such as roaming and collideable enemies, stationary hazards like spikes, health, score, and respawn systems, and maybe some other intractable objects, like coins to collect.
Not sure what I&amp;rsquo;ll do first, probably the latter, as it will teach me how to add collideable items to the world that should not be treated as platforms.&lt;/p&gt;
&lt;div class=&#34;comment-link&#34;&gt; &lt;a href=&#34;mailto:%61%6e%64%72%65%79%6f%72%73%74%2b%62%6c%6f%67%40%67%6d%61%69%6c%2e%63%6f%6d?subject=Comment: Game1 W3/4 (again)&#34; target=&#34;_blank&#34; rel=&#34;noopener noreferrer&#34;&gt;Comment via email&lt;/a&gt;&lt;/div&gt;</description>
      <pubDate>Sat, 22 Jul 2023 21:03:00 +0300</pubDate>
    </item><item>
      <title>Game1 W3/4</title>
      <link>https://andreyor.st/posts/2023-07-17-game1-w34/</link>
      <guid>https://andreyor.st/posts/2023-07-17-game1-w34/</guid>
      <description>&lt;p&gt;Long story short, no progress so far.
I&amp;rsquo;m starting to think that the way I&amp;rsquo;ve set this challenge up actually mostly demotivates me to continue rather than encourages me to press forward.
To say that I&amp;rsquo;ve been working on Game1 will be both a lie to myself and to you.
Well, I did, but in a tinkerer&amp;rsquo;s fashion - rewriting one piece of code til I&amp;rsquo;m satisfied (except I never am).&lt;/p&gt;
&lt;p&gt;So far I&amp;rsquo;ve managed to put sprites into the TIC-80 engine and created a simple controller, that actually doesn&amp;rsquo;t work how I expected it to, and that&amp;rsquo;s it:&lt;/p&gt;
&lt;figure class=&#34;pixelart&#34;&gt;&lt;img src=&#34;https://andreyor.st/2023-07-17-game1-w34/player-controller.gif&#34;/&gt;
&lt;/figure&gt;

&lt;p&gt;Mostly because for the past two weeks I was either distracted by work or by working on a new Emacs window manager described in the last post (that, probably, no one needs, even me).&lt;/p&gt;
&lt;p&gt;I have two weeks left (including this one), and I don&amp;rsquo;t think I&amp;rsquo;ll be able to finish this one in time, at least in its full shape.
Maybe I&amp;rsquo;ll settle on a simpler thing, like a demo in which I just explore how to write a platformer, it&amp;rsquo;ll still be a good start.&lt;/p&gt;
&lt;div class=&#34;comment-link&#34;&gt; &lt;a href=&#34;mailto:%61%6e%64%72%65%79%6f%72%73%74%2b%62%6c%6f%67%40%67%6d%61%69%6c%2e%63%6f%6d?subject=Comment: Game1 W3/4&#34; target=&#34;_blank&#34; rel=&#34;noopener noreferrer&#34;&gt;Comment via email&lt;/a&gt;&lt;/div&gt;</description>
      <pubDate>Mon, 17 Jul 2023 23:07:00 +0300</pubDate>
    </item><item>
      <title>Fennel Game Jam 2022</title>
      <link>https://andreyor.st/posts/2022-06-12-fennel-game-jam-2022/</link>
      <guid>https://andreyor.st/posts/2022-06-12-fennel-game-jam-2022/</guid>
      <description>&lt;p&gt;Quite recently a Fennel game jam happened on &lt;a href=&#34;https://itch.io/&#34; target=&#34;_blank&#34;&gt;itch.io&lt;/a&gt; and I&amp;rsquo;ve decided to participate.
I&amp;rsquo;ve been part of the Fennel community for some years, and every once in a while a lot of people from this community participated in a lisp game jam, but I&amp;rsquo;ve never made a game before, so I&amp;rsquo;ve skipped these events.
However, when the Fennel-specific game jam was announced I decided that I must finally put myself together and participate.&lt;/p&gt;
&lt;p&gt;The rules of the jam were quite relaxed - you basically had to use fennel for game programming, and release the game under an &lt;a href=&#34;https://opensource.org/licenses&#34; target=&#34;_blank&#34;&gt;OSI approved license&lt;/a&gt;.
No theme, no engine restrictions, no size requirement - an ideal situation for a programmer like me, who doesn&amp;rsquo;t know how to make a game, at all.
When I was young, and I mean, like almost 20 years ago, I only toyed with &lt;a href=&#34;https://en.wikipedia.org/wiki/Adobe_Flash#Macromedia&#34; target=&#34;_blank&#34;&gt;Macromedia Flash&lt;/a&gt; and ActionScript 2.0 at my school computer class, making interactive cartoons, which can hardly be considered games.
It&amp;rsquo;s not like I&amp;rsquo;m a complete stranger to game programming though, we had a very brief introduction to game programming at the university, except we never actually programmed games during these courses.
So, for example, I know what delta time is, how it should be used, or how to do vector math needed for basic movement, but this doesn&amp;rsquo;t count as experience.
If you read my blog, you may remember my small projects like &lt;a href=&#34;https://andreyor.st/posts/2020-06-04-simple-ray-casting-with-clojurescript/&#34;&gt;raycasting&lt;/a&gt; and &lt;a href=&#34;https://andreyor.st/posts/2020-10-15-raymarching-with-fennel-and-love/&#34;&gt;raymarching&lt;/a&gt;, which feature movement, camera programming, basic collision detection, and a basic game-loop.
So actually I felt prepared when I decided to enter the jam.&lt;/p&gt;
&lt;p&gt;I&amp;rsquo;ve already tried LÖVE2D in the mentioned raymarching example, so I&amp;rsquo;ve decided to change things up a bit since I&amp;rsquo;ve wanted to explore another project - &lt;a href=&#34;https://tic80.com/&#34; target=&#34;_blank&#34;&gt;TIC-80&lt;/a&gt;.
The change from LÖVE2D to TIC-80 actually made it easier for me, since I could do the sprite-work and sound effects inside tic&amp;rsquo;s inbuilt editors.
So I&amp;rsquo;ve begun to work on it.&lt;/p&gt;
&lt;p&gt;I&amp;rsquo;ll try to structure this post as a series of days based on the notes I made during the event.
There probably will not be any code though, as I have no early versions, since I&amp;rsquo;ve opted out of using version control systems for this project, to keep the retro vibe of the fantasy computer.&lt;/p&gt;
&lt;h2 id=&#34;day-1&#34;&gt;Day 1&lt;/h2&gt;
&lt;p&gt;The hardest day in my opinion because it is the day when I &lt;strong&gt;have&lt;/strong&gt; to start working on a project I haven&amp;rsquo;t had any thoughts about beforehand.
I&amp;rsquo;ve sat down and thought about what kind of game I &lt;em&gt;can&lt;/em&gt; make in the limited time of the event which was going from May 25 to Jun 1st.
Given 5 days and almost no game development experience, I&amp;rsquo;ve decided to play it safe and make a Breakout-like game.
Though a plain breakout clone would be a bit boring, so I&amp;rsquo;ve decided that I&amp;rsquo;ll put a small twist once you destroy the final block which would shake things a bit and hopefully be a welcome surprise.&lt;/p&gt;
&lt;p&gt;Now, since the theme was chosen I at least knew what I needed to start - a ball, a platform, and a box to bounce the ball to prevent it from flying away to infinity.
But I didn&amp;rsquo;t know how to do anything in tic, so I&amp;rsquo;ve opened up a &lt;a href=&#34;https://tic80.com/learn&#34; target=&#34;_blank&#34;&gt;manual&lt;/a&gt;.
The manual is minimalist, to say the least, and doesn&amp;rsquo;t provide a lot of info beyond essential things.
So I&amp;rsquo;ve picked up the &lt;code&gt;circ&lt;/code&gt; &lt;code&gt;rect&lt;/code&gt; and &lt;code&gt;rectb&lt;/code&gt; functions and started working.
This was my first result:&lt;/p&gt;
&lt;p&gt;&lt;video autoplay loop muted&gt;&lt;source src=&#34;https://andreyor.st/2022-06-12-fennel-game-jam-2022/day1.mp4&#34; type=&#34;video/mp4&#34;&gt;
Your browser does not support the video tag.&lt;/video&gt;&lt;/p&gt;
&lt;p&gt;This has very basic hard-coded physics, meaning that I&amp;rsquo;ve literally defined a vector of all ball velocities and just cycled through it when the ball collided with any world object.
But it already has some interesting stuff done, which I haven&amp;rsquo;t changed much afterward.&lt;/p&gt;
&lt;p&gt;The first thing is that It has a proper world to screen coordinate transformation, which I&amp;rsquo;ve kinda never done before.
It&amp;rsquo;s a simple thing, I just have a world X and Y offsets that made the game world, e.g. the box rest, not in the top left corner but 10 pixels to the right and down.
All game physics was calculated with the top left corner in &lt;code&gt;0,0&lt;/code&gt; and then translated before drawing.&lt;/p&gt;
&lt;p&gt;The platform tracks its position and makes sure not to go outside the world boundaries.
And the ball is checked for collisions with everything but the lower boundary.
There, instead of a collision a basic &lt;code&gt;game-over?&lt;/code&gt; flag is set to interrupt the game loop.&lt;/p&gt;
&lt;p&gt;I was pretty satisfied for day one, especially since it wasn&amp;rsquo;t really a full day of me programming this, but only 3 hours after work.
The jam started at 10:00 AM in my time zone, and my work day also starts at the same time, so I had to begin working on a game 9 hours after the start of the jam.
I&amp;rsquo;m sure this is a common situation for most other participants, though perhaps they have had time before work too.&lt;/p&gt;
&lt;h2 id=&#34;day-2&#34;&gt;Day 2&lt;/h2&gt;
&lt;p&gt;I knew that I&amp;rsquo;ll not be able to move forward with the current physics system I&amp;rsquo;ve added to the game, so I spent most of the second day fighting with the collision system.
This was the result I got:&lt;/p&gt;
&lt;p&gt;&lt;video autoplay loop muted&gt;&lt;source src=&#34;https://andreyor.st/2022-06-12-fennel-game-jam-2022/day2.mp4&#34; type=&#34;video/mp4&#34;&gt;
Your browser does not support the video tag.&lt;/video&gt;&lt;/p&gt;
&lt;p&gt;This had really wonky surface normal calculations, and you can see that the ball isn&amp;rsquo;t particularly happy with the results, as it just decides to leave the game.
I wasn&amp;rsquo;t happy with it either, but it was a move in the right direction.&lt;/p&gt;
&lt;p&gt;In addition to &lt;em&gt;improved&lt;/em&gt; physics, I&amp;rsquo;ve added destructible bricks with a random amount of health represented by a color.
The color was an accident, as I used brick health in place of the brick color when bricks were drawn on the screen, but I actually liked it.
The health was distributed randomly, but I&amp;rsquo;ve been focused on physics, so I&amp;rsquo;ve left it as is.&lt;/p&gt;
&lt;p&gt;There were two main problems.
First, I&amp;rsquo;ve re-used some linear algebra code from my raymarching project, mainly the calculation of the distance to the box and the calculation of the normal.
However, the calculation of a normal was done for a 3D world and wasn&amp;rsquo;t as meaningful for 2D space.
I&amp;rsquo;m not sure whether I&amp;rsquo;ll be able to explain the general method, but the idea is that I&amp;rsquo;ve found an intersection point, and picked two more points one below the surface and to the left and another above the surface and to the right.
Then I used some vector math to find a normal based on distance estimation from these points to the intersection point.
This works quite well for 3D, given that all reflections in the raymarching demo were correct, but in 2D it resulted in weird unexpected reflections (not in the video).&lt;/p&gt;
&lt;p&gt;Unfortunately, I have this kind of problem that once I&amp;rsquo;ve fixated on some solution I can&amp;rsquo;t see another one until I step away from the problem for a bit.
So I&amp;rsquo;ve kept the current physics and ended the day 2 session.&lt;/p&gt;
&lt;h2 id=&#34;day-3&#34;&gt;Day 3&lt;/h2&gt;
&lt;p&gt;This day was mostly spent polishing and rewriting the collision system.
I&amp;rsquo;ve almost fully fixed bugs with unexpected angles, but the ball still managed to find its way inside the platform.
I couldn&amp;rsquo;t spend too much time on it, so I&amp;rsquo;ve decided to leave it as is, and maybe fix it after everything else is done.&lt;/p&gt;
&lt;p&gt;In addition to that, I&amp;rsquo;ve made it such that the block health depends on the row it was spawned at.
So the lower blocks had 1 health, the next row above it had 2 health, and so on.
This can be seen with this nice color transition from purple to orange.&lt;/p&gt;
&lt;p&gt;&lt;video autoplay loop muted&gt;&lt;source src=&#34;https://andreyor.st/2022-06-12-fennel-game-jam-2022/day3.mp4&#34; type=&#34;video/mp4&#34;&gt;
Your browser does not support the video tag.&lt;/video&gt;&lt;/p&gt;
&lt;p&gt;I&amp;rsquo;ve also done some basic point systems that added 100 points when you destroy a brick, and 10 points upon collision.
There are now lives, so you can lose the ball several times before the &amp;ldquo;game over&amp;rdquo;.
Satisfied with that I&amp;rsquo;ve ended the third day, realizing that theirs not that much time left, but also not too far til the game will be complete.&lt;/p&gt;
&lt;p&gt;For the next two days, I was planning to add graphics and a small twist that will hopefully make the game a bit more interesting.&lt;/p&gt;
&lt;h2 id=&#34;day-4&#34;&gt;Day 4&lt;/h2&gt;
&lt;p&gt;Up until this day I&amp;rsquo;ve drawn everything with basic rectangles and this wasn&amp;rsquo;t going to cut for the jam in my opinion.
So for the fourth day, I&amp;rsquo;ve decided to focus on some graphics and sounds.&lt;/p&gt;
&lt;p&gt;The inbuilt sprite editor is quite good, actually.
I&amp;rsquo;ve only tried the &lt;a href=&#34;https://github.com/LibreSprite/LibreSprite&#34; target=&#34;_blank&#34;&gt;LibreSprite&lt;/a&gt;, an open-source fork of aseprite sprite editor, and it was a bit sluggish, perhaps because it was using a software cursor, which felt unresponsive.
But with TIC&amp;rsquo;s sprite editor it wasn&amp;rsquo;t a problem and I quickly drew basic sprites for bricks, the platform, and the ball:&lt;/p&gt;
&lt;figure&gt;&lt;img src=&#34;https://andreyor.st/2022-06-12-fennel-game-jam-2022/sprites.gif&#34;
         alt=&#34;Figure 1: Sprites&#34;/&gt;&lt;figcaption&gt;
            &lt;p&gt;&lt;span class=&#34;figure-number&#34;&gt;Figure 1: &lt;/span&gt;Sprites&lt;/p&gt;
        &lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p&gt;Then I decided to add power-ups.&lt;/p&gt;
&lt;p&gt;There are three classic power-ups that affect the size of the ball or the platform - the big ball, the long platform, and the short platform.
All these powers are timer-based, with a small exception for the big ball - if you lose the ball while it&amp;rsquo;s big, the power-up is also lost.
In addition to that, there are two speed-related power-ups, one that increases the ball&amp;rsquo;s speed, and one that decreases it.&lt;/p&gt;
&lt;p&gt;Speaking of speed, I&amp;rsquo;ve also made it so that the speed of the ball gradually grows during the game up to twice the initial speed.
And there&amp;rsquo;s a mentioned power-up to increase and decrease the value by a quarter.&lt;/p&gt;
&lt;p&gt;Here&amp;rsquo;s a demonstration of various power-ups:&lt;/p&gt;
&lt;p&gt;&lt;video autoplay loop muted&gt;&lt;source src=&#34;https://andreyor.st/2022-06-12-fennel-game-jam-2022/day4.mp4&#34; type=&#34;video/mp4&#34;&gt;
Your browser does not support the video tag.&lt;/video&gt;&lt;/p&gt;
&lt;p&gt;As can be seen, the ball still can act weirdly, almost spinning around the platform, but I&amp;rsquo;ve already spent most of my time on graphics and sounds, so I&amp;rsquo;ve decided to left it for the last day.&lt;/p&gt;
&lt;h2 id=&#34;day-5&#34;&gt;Day 5&lt;/h2&gt;
&lt;p&gt;The last day of the jam!
I&amp;rsquo;ve added more graphics, as I wasn&amp;rsquo;t able to figure out how to measure things in seconds, as the values of the &lt;code&gt;time&lt;/code&gt; function were kinda &lt;em&gt;weird&lt;/em&gt;.
So I&amp;rsquo;ve opted out of colored bars that gradually decrease in length.
I&amp;rsquo;ve also decided to add some effects, like screen shake when the ball is big.
Here&amp;rsquo;s how it looks:&lt;/p&gt;
&lt;p&gt;&lt;video autoplay loop muted&gt;&lt;source src=&#34;https://andreyor.st/2022-06-12-fennel-game-jam-2022/day5.mp4&#34; type=&#34;video/mp4&#34;&gt;
Your browser does not support the video tag.&lt;/video&gt;&lt;/p&gt;
&lt;p&gt;At this point, I&amp;rsquo;ve started working on the &lt;em&gt;twist&lt;/em&gt; and polishing things out.
For the twist I&amp;rsquo;ve decided to do a boss battle at the end of the game, essentially turning the Breakout game into Pong when the last brick is destroyed:&lt;/p&gt;
&lt;p&gt;&lt;video autoplay loop muted&gt;&lt;source src=&#34;https://andreyor.st/2022-06-12-fennel-game-jam-2022/boss-battle.mp4&#34; type=&#34;video/mp4&#34;&gt;
Your browser does not support the video tag.&lt;/video&gt;&lt;/p&gt;
&lt;p&gt;The boss has a fixed amount of health, and each hit with the ball removes some amount of it.
If the big ball power-up is active the ball deals twice the damage.
I&amp;rsquo;ve actually made it that this damage multiplier also affects bricks, so bricks are destroyed a bit faster now too.
And if you score the boss, it&amp;rsquo;ll lose 5 times hp of a single hit, although it&amp;rsquo;s almost impossible, unless you picked a lot of speed power-ups.&lt;/p&gt;
&lt;p&gt;Boss&amp;rsquo; health is represented with another bar that changes color depending on the health range, going from green at full to red near the end.
To win the game you must defeat the boss, which is actually a bit tricky and I can&amp;rsquo;t do it consistently myself either.
Speaking of power-ups, there&amp;rsquo;s a random chance to spawn a power-up in a random location during the boss battle.
The chance of spawning is very low though.&lt;/p&gt;
&lt;p&gt;I&amp;rsquo;ve uploaded the game to itch as a web build, and noticed a weird problem.
All power-ups were wearing off too fast!
I couldn&amp;rsquo;t understand what the problem is, because to my knowledge I did a proper way of handling power-up times - I obtained the current game-time with the call to the &lt;code&gt;time&lt;/code&gt; function, added a fixed amount to it, and checked on each frame if the value returned by &lt;code&gt;time&lt;/code&gt; surpassed it.
To simplify, upon activation of a timer based power-up, I did this &lt;code&gt;end = time() + powerup_duration&lt;/code&gt;, and checked for &lt;code&gt;end &amp;lt; time()&lt;/code&gt; to disable the power-up.
Given that it is based on &lt;code&gt;time&lt;/code&gt; and not on some ticking mechanism, I&amp;rsquo;ve thought that it should work equally well regardless of frames per second.
Even more weird was that the ball speed was the same as on the desktop.
So in short - power-ups ended a lot faster, yet the rest of the speed was the same.&lt;/p&gt;
&lt;p&gt;I&amp;rsquo;ve decided to do it &amp;ldquo;properly&amp;rdquo; and calculated the delta time.
Then I multiplied ball movement by newly calculated &lt;code&gt;dt&lt;/code&gt; and reworked power-ups to work based on ticks, meaning they weren&amp;rsquo;t tied to &lt;code&gt;time&lt;/code&gt; directly.
Checked that all works well on my desktop I&amp;rsquo;ve made a fresh web build, uploaded it to itch, and it was way too fast!
The ball speed now was superfast, power-ups ended almost immediately.
After a ton of debugging, and trying to make any sense out of the phrase that the main &lt;code&gt;TIC&lt;/code&gt; function is called 60 times per second, when it &lt;em&gt;clearly&lt;/em&gt; wasn&amp;rsquo;t, as the game speed was different between the web and Linux builds.
Then I made a Windows build, and it worked with the same speed as the web build, and I thought that maybe it was a bug in the Linux build.
I&amp;rsquo;ve checked what value of &lt;code&gt;dt&lt;/code&gt; was calculated on each platform, and the Linux build had a 10-fold difference from other platforms:&lt;/p&gt;
&lt;figure&gt;&lt;img src=&#34;https://andreyor.st/2022-06-12-fennel-game-jam-2022/tic-bug.png&#34;
         alt=&#34;Figure 2: Delta time differences between platforms&#34;/&gt;&lt;figcaption&gt;
            &lt;p&gt;&lt;span class=&#34;figure-number&#34;&gt;Figure 2: &lt;/span&gt;Delta time differences between platforms&lt;/p&gt;
        &lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p&gt;The Linux build had a delta time value of approximately &lt;code&gt;0.001&lt;/code&gt; while the web and Windows build had &lt;code&gt;0.02&lt;/code&gt;.
I&amp;rsquo;ve tried an older version of TIC, and it had no such issue but didn&amp;rsquo;t support the Fennel version I was using for the game.
So I had to distribute the game only as a web build until the &lt;a href=&#34;https://github.com/nesbox/TIC-80/issues/1904&#34; target=&#34;_blank&#34;&gt;issue&lt;/a&gt; is fixed.
I&amp;rsquo;ve tweaked the speed by adding a &lt;code&gt;game_speed&lt;/code&gt; parameter that I could tweak around inside the web build to make the speed feel right.
After that, I pushed a final web build with these tweaks and submitted the game.&lt;/p&gt;
&lt;h2 id=&#34;jam-results&#34;&gt;Jam results&lt;/h2&gt;
&lt;p&gt;Because of my time zone, I was the first who submitted a game, but I couldn&amp;rsquo;t do any more work because the last hours of the jam were at nighttime here.
Out of 21 participants, only seven games were submitted, which actually a pretty great result, in my opinion, it&amp;rsquo;s a solid third!
Funnily enough, we have about 60 people in our &lt;code&gt;#fennel&lt;/code&gt; IRC channel on &lt;a href=&#34;https://libera.chat/&#34; target=&#34;_blank&#34;&gt;Libera.Chat&lt;/a&gt;, so 21 people is a third too.
The results can be viewed &lt;a href=&#34;https://itch.io/jam/fennel-game-jam-1/results&#34; target=&#34;_blank&#34;&gt;here&lt;/a&gt;, and I suggest you try all games, as there are pretty cool entries!&lt;/p&gt;
&lt;p&gt;I managed to score third place, which actually surprised me a bit, given how simple the concept of my game was.
But I&amp;rsquo;m glad, and I think it&amp;rsquo;s a solid result for the first game.&lt;/p&gt;
&lt;p&gt;In retrospect, I think I could do some things better, especially in the user interface department.
Adding a proper menu, maybe an intro, making the interface look more like an arcade cabinet, which I&amp;rsquo;ve originally attempted, but my pixel-art skills are not that great yet.
But I still like the result, I feel that for a first game it turned out pretty great.
And the process was quite fun, which is the most important thing for me.&lt;/p&gt;
&lt;p&gt;I hope I&amp;rsquo;ll participate in more lisp-themed jams, maybe in a lisp game jam or the next Fennel jam.
I&amp;rsquo;ve always wanted to make games, and I had some ideas about the game I want to make in the Metroidvania genre.
Game jams are perfect for experiments, and you can learn a lot by doing things you never did before.
I&amp;rsquo;m not going to make a whole game during the jam as I want it to be a more complete thing, but the jam can help me to flesh out some main mechanics, be it the movement, or world-building practice.
So I&amp;rsquo;m looking forward to the upcoming events!&lt;/p&gt;
&lt;p&gt;This was a lot of fun, and I hope more people will join next time!
Thanks to everyone who participated, &lt;a href=&#34;https://www.mattroelle.com/&#34; target=&#34;_blank&#34;&gt;Matt Roelle&lt;/a&gt; for organizing the Jam, and everyone involved in Fennel and TIC-80 development.&lt;/p&gt;
&lt;div class=&#34;comment-link&#34;&gt; &lt;a href=&#34;mailto:%61%6e%64%72%65%79%6f%72%73%74%2b%62%6c%6f%67%40%67%6d%61%69%6c%2e%63%6f%6d?subject=Comment: Fennel Game Jam 2022&#34; target=&#34;_blank&#34; rel=&#34;noopener noreferrer&#34;&gt;Comment via email&lt;/a&gt;&lt;/div&gt;</description>
      <pubDate>Sun, 12 Jun 2022 12:37:00 +0300</pubDate>
    </item>
  </channel>
</rss>
