<?xml version="1.0"?>
<feed xmlns="http://www.w3.org/2005/Atom"><title>daymare.net</title><id>https://daymare.net/</id><updated>2026-06-10T21:48:41.255413+00:00</updated><author><name>daymare</name><uri>https://daymare.net/</uri></author><link href="https://daymare.net/" rel="self"/><entry><title>DAYGPT</title><id>https://daymare.net/demos/daygpt</id><updated>2026-06-10T21:47:34.772763855+00:00</updated><author><name>daymare</name><uri>https://daymare.net/</uri></author><category term="DEMOS"/><link href="https://daymare.net/demos/daygpt" rel="alternate"/><published>2026-06-10T21:47:34.772763855+00:00</published><summary>&lt;p&gt;A public Q&amp;amp;A board where anyone can ask a question, and answers are posted here for everyone to see. You might ask why &apos;GPT&apos; when I&apos;m the one answering the questions, simple!&lt;/p&gt;</summary><content type="html">&lt;p&gt;A public Q&amp;amp;A board where anyone can ask a question, and answers are posted here for everyone to see. You might ask why &apos;GPT&apos; when I&apos;m the one answering the questions, simple!
&lt;img src=&quot;https://daymare.net/demos/daygpt/./assets/image.png&quot; alt=&quot;discord message of my friend saying &apos;you found it before ai&apos;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;It&apos;s because I&apos;m better than AI&lt;/p&gt;
&lt;p&gt;This was a super silly project, oh my god I loved it so much. I&apos;ve had a sayout link on my twitter account for a good while now, but I thought now that I&apos;ve got this demo system why not have my own server?&lt;/p&gt;
&lt;p&gt;I know you probably wouldn&apos;t guess this but this was my first time experimenting with servers. In fact, to get this to work I setup my home mac mini as a cloudflare server tunnel. So put your name in or stay anonymous, but either way ask something! That&apos;s the only way I&apos;ll be able to get ROI on the time I spent on this.&lt;/p&gt;
&lt;p&gt;Oh also, your messages are NOT visible to anyone else but you until they get answered. This is to prevent internet people being internet people and ruining everything. But I promise I&apos;ll get to it!&lt;/p&gt;
&lt;p&gt;If you want to support me, &lt;a href=&quot;https://daymare.net/support&quot;&gt;I have a ko-fi&lt;/a&gt; and also &lt;a href=&quot;https://daymare.net/community&quot;&gt;a discord server&lt;/a&gt; where you can ask me more questions.&lt;/p&gt;
&lt;h2&gt;Tech&lt;/h2&gt;
&lt;p&gt;I&apos;m not really a huge web fan, but I thought it&apos;d have been absurd to spin up a whole WASM app for a silly project like this so I used Javascript.&lt;/p&gt;
&lt;p&gt;Not any of the fancy stuff though, this is just a plain ol&apos; Javascript and HTML app.&lt;/p&gt;
&lt;p&gt;I&apos;ve also integrated it with &lt;a href=&quot;https://www.dicebear.com/&quot;&gt;DiceBear&lt;/a&gt; so I could generate cool profile pictures for people based on their usernames.&lt;/p&gt;
&lt;p&gt;I also have a cool little admin dashboard, don&apos;t get too happy about it though I have a very secure 256bit key. I&apos;m gonna regret saying this aren&apos;t I..&lt;/p&gt;
&lt;p&gt;While writing that I started wondering how many ways people could abuse this and I noticed I didn&apos;t have a length limit. Well, I do now! It&apos;s 4096 characters, though the client side doesn&apos;t reflect it right now so... let&apos;s hope no one talks too long&lt;/p&gt;
&lt;h2&gt;Privacy and data stuff&lt;/h2&gt;
&lt;p&gt;I don&apos;t really need this but just to be transparent:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;your username is not unique, it cannot be linked to you. Anyone can have any username. Its mostly there to create conversation and a silly avatar.&lt;/li&gt;
&lt;li&gt;I store the messages you send, alongside the username you gave at that time.&lt;/li&gt;
&lt;/ul&gt;
</content></entry><entry><title>RAVIOLI: Tower Defense</title><id>https://daymare.net/demos/ravioli</id><updated>2026-06-09T14:48:47.251195650+00:00</updated><author><name>daymare</name><uri>https://daymare.net/</uri></author><category term="DEMOS"/><link href="https://daymare.net/demos/ravioli" rel="alternate"/><published>2026-06-09T14:48:47.251195650+00:00</published><summary>&lt;p&gt;A factory building tower defense game. Oh boy where do I even start. This has been a long project that has gone through many, many iterations.&lt;/p&gt;</summary><content type="html">&lt;p&gt;A factory building tower defense game. Oh boy where do I even start. This has been a long project that has gone through many, many iterations.&lt;/p&gt;
&lt;p&gt;The core idea of making a factory game has been with me for a good while now and I&apos;ve been working on it for about a year at this point. But ravioli itself, I&apos;ve been working on for the past 6 months.&lt;/p&gt;
&lt;p&gt;Initially, this was meant to be a 3D game, with a voxel engine. And while the tech of a voxel engine was evidently very interesting to me (I mean, I&apos;ve written &lt;a href=&quot;/blogs/godot-ruined-me&quot;&gt;not one&lt;/a&gt;, &lt;a href=&quot;/blogs/voxel-engine-in-a-weekend&quot;&gt;but two posts&lt;/a&gt; about it. The latter of which was an in-depth guide on how to make one, even if not optimised) I wasn&apos;t able to make the gameplay feel enjoyable. Primarily because of the 3D grid-based nature of it combined with me not being able to think outside of the box (heh).&lt;/p&gt;
&lt;p&gt;But I think this 2D version we have now works great. I&apos;ve been working on it and really enjoying it a ton. More about the tech behind it after you check it out.&lt;/p&gt;
&lt;h2&gt;The Tech&lt;/h2&gt;
&lt;p&gt;If you&apos;ve ever read my previous blog posts, you probably already know I&apos;m very comfortable not using a game engine. Ergo, I didn&apos;t use one for ravioli either. The game is completely written in Rust, using &lt;a href=&quot;https://crates.io/crates/wgpu&quot;&gt;wgpu&lt;/a&gt; for graphics and &lt;a href=&quot;https://crates.io/crates/winit&quot;&gt;winit&lt;/a&gt; for window management.&lt;/p&gt;
&lt;p&gt;Other than that, the rest is pretty standard.&lt;/p&gt;
&lt;p&gt;I think I should address a question I get surprisingly a lot for someone who has 30 people in their discord: why not use a Rust game engine.&lt;/p&gt;
&lt;p&gt;Or, more specifically, why not use &lt;code&gt;bevy&lt;/code&gt;?
For the unacquainted: &lt;code&gt;bevy&lt;/code&gt; is, in their own words, &amp;quot;A refreshingly simple data-driven game engine built in Rust&amp;quot;.&lt;/p&gt;
&lt;p&gt;The reason I didn&apos;t use bevy is because I don&apos;t think an ECS is the correct driver for my game. At its core, ravioli is a very simple game. You have structures like assemblers, belts, turrets, and you have entities like bullets, enemies, explosives.&lt;/p&gt;
&lt;p&gt;Obviously there are different ways these things interact with each other, for example while all other structures can be evaluated in pretty much any order belts need to be evaluated in a certain order to ensure items flow correctly. Or Martyr nodes need special handling so that you can form health networks without them destroying each other.&lt;/p&gt;
&lt;p&gt;But nevertheless, none of these would benefit from an ECS. They&apos;re entities with systems yes, but I have no need for components and what little need I might have is trivially solvable by using &lt;a href=&quot;https://doc.rust-lang.org/reference/items/enumerations.html#r-items.enum.constructor&quot;&gt;tagged unions&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;You could argue it&apos;d make the game faster, more performant. And you might be right, it would maybe allow you to get 50,000 entities at 60fps instead of 10,000. A 5x difference! It might make the code a bit more complicated but come on, a FIVE TIMES DIFFERENCE IN PERFORMANCE!&lt;/p&gt;
&lt;p&gt;But also, in ravioli maps are small and contained. It&apos;s not open world with an infinitely vast horizon.&lt;/p&gt;
&lt;p&gt;But also, if it was open-world like for example factorio, I wouldn&apos;t want a generic ECS like &lt;code&gt;bevy&lt;/code&gt; either. I&apos;d hand-roll my own system that&apos;s specialised for my usecase.&lt;/p&gt;
&lt;p&gt;I think it&apos;s amazing that we have engines that people can just pick up and use, I mean for a good portion of my life I&apos;ve used &lt;a href=&quot;https://www.raylib.com/&quot;&gt;raylib&lt;/a&gt; for everything because I didn&apos;t want to learn OpenGL. But I don&apos;t think &lt;code&gt;bevy&lt;/code&gt; is the right choice for me.&lt;/p&gt;
&lt;p&gt;If you&apos;re interested, I recommend reading &lt;a href=&quot;https://loglog.games/blog/leaving-rust-gamedev/&quot;&gt;this&lt;/a&gt; by LogLog Games. It&apos;s quite a long read about Rust and gamedev. If you have the time, definitely read all of it it, but if you only have 5 minutes, &lt;a href=&quot;https://loglog.games/blog/leaving-rust-gamedev/#ecs-solves-the-wrong-kind-problem&quot;&gt;at least read the section about ECS&lt;/a&gt;.&lt;/p&gt;
&lt;h2&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;ravioli is currently in development, so I&apos;d absolutely love to hear what you think. So please join us in &lt;a href=&quot;https://daymare.net/community&quot;&gt;my discord server&lt;/a&gt; and if you want to support the development monetarily, &lt;a href=&quot;https://daymare.net/support&quot;&gt;I have a ko-fi page&lt;/a&gt;!&lt;/p&gt;
&lt;p&gt;Thank you for reading, and I hope you enjoyed the game!&lt;/p&gt;
</content></entry><entry><title>One of Those Bugs</title><id>https://daymare.net/blogs/one-of-those-bugs</id><updated>2025-12-07T08:03:27.243156829+00:00</updated><author><name>daymare</name><uri>https://daymare.net/</uri></author><category term="BLOGS"/><link href="https://daymare.net/blogs/one-of-those-bugs" rel="alternate"/><published>2025-12-07T08:03:27.243156829+00:00</published><summary>&lt;p&gt;You know how every project has one of &lt;em&gt;those&lt;/em&gt; bugs that make you question whether or not it&apos;s even worth it to continue?&lt;/p&gt;</summary><content type="html">&lt;p&gt;You know how every project has one of &lt;em&gt;those&lt;/em&gt; bugs that make you question whether or not it&apos;s even worth it to continue?&lt;/p&gt;
&lt;p&gt;Yeah, me too. From recent memory, not including today&apos;s, my &lt;a href=&quot;../speedrunning-a-cpu/&quot;&gt;RISC-V Emulator&lt;/a&gt; project got abandoned because I couldn&apos;t figure out a bug that was happening with virtual memory. But I was working on &lt;a href=&quot;https://github.com/todaymare/margarine&quot;&gt;margarine&lt;/a&gt; this week (&lt;a href=&quot;../four-years-five-failures-one-compiler/&quot;&gt;read more here&lt;/a&gt;) and I&apos;m not going to abandon margarine again. It&apos;s the longest running toxic relationship in my life.&lt;/p&gt;
&lt;p&gt;I hear you, it&apos;s weird to write a whole post about a single bug. But it&apos;s my blog page and I&apos;ve survived the gauntlet to Olympus and I&apos;m going to tell the tale damn it.&lt;/p&gt;
&lt;p&gt;Let me set the scene, I&apos;ll try not to yap too much.&lt;/p&gt;
&lt;p&gt;It was the start of this week, Advent of Code had just started¹, so as any good compiler-dev does I went ahead and started solving it day-to-day in margarine. Day 1 went smooth, I had to add a bunch of features to margarine such as tuple destructuring, multiple standard library functions and a LOT of bug fixes that were relatively easy to do. And then, day 2 arrived.&lt;/p&gt;
&lt;p&gt;Part 1 went relatively smoothly, too smoothly. I had the answer &lt;code&gt;40055209690&lt;/code&gt;.
&lt;img src=&quot;https://daymare.net/blogs/one-of-those-bugs/./assets/day2-part1.png&quot; alt=&quot;day2 part1 solution&quot; /&gt;&lt;/p&gt;
&lt;p&gt;I moved onto part 2. I wrote it, compiled it to see if there were any compilation errors. See, the thing with margarine is that right now I don&apos;t have a separate build command, I just do &lt;code&gt;run&lt;/code&gt; and it compiles it &amp;amp; runs. So when I compiled it to check for errors inevitably the part 1 code also ran.&lt;/p&gt;
&lt;p&gt;There were no errors. But umm, why did it print out &lt;code&gt;47353585426&lt;/code&gt;?&lt;/p&gt;
&lt;p&gt;And this is where my journey started. For some reason, adding new code broke existing code. I do mean &lt;em&gt;adding new code&lt;/em&gt;, not &lt;em&gt;executing new code&lt;/em&gt; because the part 2 solution wasn&apos;t even in the main path yet, it was just inside the file.&lt;/p&gt;
&lt;p&gt;Okay, let&apos;s see. The program is very closure &amp;amp; generic heavy. So I thought, alright, the problem is probably with the way I handle monomorphization which led me to go ahead and disable the function cache. Before, when I saw &lt;code&gt;foo::&amp;lt;int&amp;gt;()&lt;/code&gt; I checked a HashMap to see if that function had already been generated. After disabling the function cache I was just creating a new function instead of reusing a newly generated one. That fixed it! But uhh, why?&lt;/p&gt;
&lt;p&gt;I went to investigate, because I can&apos;t just leave the function cache off! That&apos;d generate way too big code files for my singular user!&lt;/p&gt;
&lt;p&gt;I have a custom hash function for symbols in margarine. Because margarine has a slightly more complicated type inference system than the simplest of type systems a symbol can be a type variable. We can&apos;t just hash a type variable directly, otherwise two generics that resolve to the same type would get different hashes, and the cache would explode.&lt;/p&gt;
&lt;p&gt;Which meant that the function cache HashMap is a HashMap of TypeHash and Function. Which means that it can no longer protect us from hash collisions. Aha! The problem is that we&apos;re getting a hash collision! That&apos;s a reasonable conclusion, 18 quintillion possible numbers and just my luck is a hash collision.&lt;/p&gt;
&lt;p&gt;I added a simple check to the &lt;code&gt;get_func&lt;/code&gt; function. Well, that&apos;s not the problem. Okay, how about closures? I mean the code is very closure heavy so if functions in general aren&apos;t the problem maybe closures are. Because when I disabled the function cache I also had to disable it for closures as well!&lt;/p&gt;
&lt;p&gt;Aha! (chatgpt ahh emotes bro) Closures are only hashed based on the closures type + expression id. Maybe it&apos;s not accounting for generics and captures, though if the expression id is unique that should account for captures as well. Regardless, doesn&apos;t hurt to try!&lt;/p&gt;
&lt;p&gt;I went ahead and added everything a closure could want to its hash but yet the problem was still there. Then I had the thought, maybe the closure isn&apos;t getting uniquely hashed because everything is still in generics! The issue is in how I&apos;ve implemented generics! That&apos;s why turning off the function cache fixed the problem, it removed generics from the equation!.&lt;/p&gt;
&lt;p&gt;So the way I&apos;ve implemented generics is quite possibly the dumbest way ever. I just register them as empty structs in the type table when type checking the function and when it comes to code generation I just create a name to type mapping. Maybe somewhere it&apos;s not mapping properly!&lt;/p&gt;
&lt;p&gt;Look, I would not do it this way if I were making it now but margarine has been going on and off for 2 years or so, it&apos;s basically legacy code for me.&lt;/p&gt;
&lt;p&gt;I went back and added a custom &lt;code&gt;Generic&lt;/code&gt; variant to container types, so now I still register them as symbols to the type table but at least I know that they are generics and not just empty structs. Then, I went to the &lt;code&gt;get_func&lt;/code&gt; function and added an assertion to make sure that the requested function is a fully resolved type.&lt;/p&gt;
&lt;p&gt;Yess! There were multiple places where this assertion failed. Field accesses, identifiers, even closures! I fixed those cases, resolved the generics completely and it compiled fully!&lt;/p&gt;
&lt;p&gt;Now, margarine&apos;s runtime is very slow right now (200-300MIPS), especially for the task day 2 was asking of me. So it took a good 30 seconds to run the entire test each time I wanted to see if it worked. I was counting for 4 garbage collection messages, because the garbage collection still had the debug prints for &amp;quot;garbage collected in 5ms&amp;quot;.&lt;/p&gt;
&lt;p&gt;The tension was high is what I&apos;m saying.&lt;/p&gt;
&lt;p&gt;garbage collected in 4.7ms&lt;br /&gt;
garbage collected in 3.5ms&lt;br /&gt;
garbage collected in 5.2ms&lt;br /&gt;
garbage collected in 4.1ms&lt;/p&gt;
&lt;p&gt;...&lt;br /&gt;
...&lt;/p&gt;
&lt;p&gt;47353585426&lt;/p&gt;
&lt;p&gt;..FFFFFU- ntastic! The problem still exists.&lt;/p&gt;
&lt;p&gt;Okay, well that whole refactor didn&apos;t fix the problem. It also didn&apos;t make it worse. Though, I can&apos;t help but wonder how my code was even running before that refactor. Like, I wasn&apos;t even resolving generics and it was working just fine. Anyways, not the time to celebrate when my program is still broken.&lt;/p&gt;
&lt;p&gt;Wait, let me check the output of the codegen.
Waiiiittt, the codegen is IDENTICAL except for the extra batch of functions.&lt;/p&gt;
&lt;h2&gt;Oh god I&apos;ve been looking at the wrong thing this whole time?&lt;/h2&gt;
&lt;p&gt;So the problem is in the runtime. Okay well, I could just output every single instruction and then figure out where the divergence happens.&lt;/p&gt;
&lt;p&gt;Except, when I tried that the output file was 14GBs and it took 15 minutes to run. Look, VSCode crashed while trying to open that file at 3GBs, NVIM couldn&apos;t even handle 2, and all that is before I even diffed it in the first place.&lt;/p&gt;
&lt;p&gt;I really doubt diffing it is going to work.&lt;/p&gt;
&lt;p&gt;This threw me for a bit of a loop. I tried running it on smaller examples but the bug never happened in them. I had to find where the difference was in a 14GB log file. Except, I don&apos;t really need detailed logs on every part of the program, do I? I just need details around where the difference started.&lt;/p&gt;
&lt;p&gt;Here&apos;s an idea, what if we hash 100k instructions and print that. That&apos;d cut down the file size by a lot! Then we can just figure out where the first difference is and add logs around there until we find the instruction that was causing the problem!&lt;/p&gt;
&lt;p&gt;Let me tell you, that worked amazingly! The problematic instruction was somewhere around 37 million instructions.
&lt;img src=&quot;https://daymare.net/blogs/one-of-those-bugs/./assets/diff-1.png&quot; alt=&quot;alt text&quot; /&gt;&lt;/p&gt;
&lt;p&gt;For some reason, that EqObj instruction was returning false.
&lt;img src=&quot;https://daymare.net/blogs/one-of-those-bugs/./assets/diff-2.png&quot; alt=&quot;alt text&quot; /&gt;&lt;/p&gt;
&lt;p&gt;Ah! The split function was failing, probably. Wait no the split function is fine. What is going on here?&lt;/p&gt;
&lt;p&gt;From this point, I don&apos;t know how to explain it much but somewhere along the way I thought &amp;quot;alright, let&apos;s reduce variance and just give it a lot of memory to work with so the GC doesn&apos;t run&amp;quot;.
&lt;img src=&quot;https://daymare.net/blogs/one-of-those-bugs/./assets/result.png&quot; alt=&quot;alt text&quot; /&gt;&lt;/p&gt;
&lt;p&gt;Oh.. So the problem is the GC? Yep!&lt;/p&gt;
&lt;p&gt;But what exactly is the problem? No idea, that&apos;s a mystery I&apos;ve yet to solve. For now, I&apos;ve just disabled the GC until I figure out how to fix it. From my perspective, the GC looks perfectly fine, and the code is extremely simple.&lt;/p&gt;
&lt;p&gt;One day, I&apos;ll fix the GC. Today, however, is not that day.&lt;/p&gt;
&lt;p&gt;If you have any theories, feel free to share them on &lt;a href=&quot;https://discord.gg/t7gNX8Kp72&quot;&gt;my discord&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;It really goes to show how hard it is to track down the source of a bug sometimes. I mean that whole function cache thing was a giant red herring!&lt;/p&gt;
&lt;p&gt;Sometimes while trying to fix a bug you fix 5 others before fixing the actual bug that got you started. Makes you wonder how your code even ran properly in the first place, huh?&lt;/p&gt;
&lt;p&gt;BTW, You can check out margarine &lt;a href=&quot;https://github.com/todaymare/margarine&quot;&gt;here&lt;/a&gt; and if you enjoyed this post &lt;a href=&quot;https://ko-fi.com/todaymare&quot;&gt;if you could spare a cup of coffee, it would be delightful&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;¹: It&apos;s only 12 days now 🥀&lt;/p&gt;
</content></entry><entry><title>But why is AI bad?</title><id>https://daymare.net/blogs/but-why-is-ai-bad</id><updated>2025-11-30T09:24:53.775846+00:00</updated><author><name>daymare</name><uri>https://daymare.net/</uri></author><category term="BLOGS"/><link href="https://daymare.net/blogs/but-why-is-ai-bad" rel="alternate"/><published>2025-11-30T09:24:53.775846+00:00</published><summary>&lt;p&gt;Lately, I&apos;ve been trying to understand our collective discomfort with AI generated content, and why we&apos;ve drawn the line where it is. Now, now, calm down, put the pitchforks down.&lt;/p&gt;</summary><content type="html">&lt;p&gt;Lately, I&apos;ve been trying to understand our collective discomfort with AI generated content, and why we&apos;ve drawn the line where it is. Now, now, calm down, put the pitchforks down.&lt;/p&gt;
&lt;p&gt;Hear me out:&lt;/p&gt;
&lt;p&gt;My main audience is programmers so I&apos;ll start there.&lt;br /&gt;
Don&apos;t worry, artists, I&apos;ll get to you, too.&lt;/p&gt;
&lt;p&gt;Say a programmer makes a tool to solve his problem, maybe to manage his config files.&lt;/p&gt;
&lt;p&gt;He goes: hmm, if this is useful to me, maybe someone else on the other side of the world in a decade or two might find it useful too! And puts it on GitHub.&lt;/p&gt;
&lt;p&gt;But he&apos;s been there: finding a really cool GitHub tool with zero documentation.&lt;/p&gt;
&lt;p&gt;That&apos;s just how it goes. You make something for you, then toss it on GitHub because well why not? But you&apos;re not gonna spend a day documenting the tool you made on a weekend, are you?&lt;/p&gt;
&lt;p&gt;So he thinks to himself, you know? Maybe not a day, but I can spare a few hours, what if I use this new AI thing to write the majority of the documentation and then just audit it. That way, if someone finds it they at least have &lt;em&gt;something&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;That sounds fine, right?&lt;br /&gt;
But I can assure you, when he&apos;s sharing his tool he&apos;s going to get flamed for using AI to generate the docs.&lt;/p&gt;
&lt;p&gt;From the outside? It&apos;s just AI slop.&lt;br /&gt;
It looks like he used it to avoid work he would&apos;ve done otherwise.&lt;br /&gt;
But we rarely consider the possibility of &amp;quot;what if it wasn&apos;t a shortcut&amp;quot;?&lt;br /&gt;
What if the AI made him spend more time because without it, the docs would have never existed in the first place?&lt;/p&gt;
&lt;p&gt;So I want to ask you, dear reader:
would it really have been better if the tool had no documentation at all?&lt;/p&gt;
&lt;p&gt;&amp;quot;Wait, you&apos;re saying AI is good?&amp;quot;&lt;br /&gt;
Yes- No? AI used to create something is better than nothing.&lt;/p&gt;
&lt;p&gt;Let&apos;s look at another discipline, art.&lt;/p&gt;
&lt;p&gt;Now I&apos;m not an artist so excuse my French here but let&apos;s take a painter who wants to explore new art styles, but can&apos;t, due to illness or injury.&lt;/p&gt;
&lt;p&gt;How harmful is it for the painter to use AI to create?&lt;br /&gt;
Is it harmful that the AI was generated on content the artists didn&apos;t consent to? Of course! But how much of it is the painter&apos;s fault?&lt;/p&gt;
&lt;p&gt;You see the pattern, yeah?&lt;br /&gt;
We have these incredible tools but we&apos;re not letting ordinary people use them.&lt;br /&gt;
We&apos;re encouraging dreams to stay dreams.&lt;/p&gt;
&lt;p&gt;What if someone uses AI to learn the guitar? Like, using it to get feedback.&lt;br /&gt;
Well yeah! That person could go to a guitar teacher instead!&lt;/p&gt;
&lt;p&gt;When we say that, aren&apos;t we gatekeeping learning to those who have the money to afford it?&lt;/p&gt;
&lt;p&gt;&amp;quot;No that&apos;s not what I meant!&amp;quot;&lt;br /&gt;
No I know. When you decided you disliked AI, you meant corporate bullshit.&lt;/p&gt;
&lt;p&gt;Firing artists, replacing programmers and voice actors, cutting corners. Selling AI generated slop at full price.&lt;/p&gt;
&lt;p&gt;Creating bullshit &amp;quot;Top 10&amp;quot; lists, expensive courses and books that are obviously auto-generated.&lt;/p&gt;
&lt;p&gt;Thinking AI replaces human talent.&lt;/p&gt;
&lt;p&gt;I agree, that&apos;s bad.&lt;/p&gt;
&lt;p&gt;But that&apos;s not me,&lt;br /&gt;
That&apos;s not you,&lt;br /&gt;
That&apos;s not them.&lt;/p&gt;
&lt;p&gt;So why are we the ones catching strays?&lt;/p&gt;
&lt;p&gt;People&apos;s anger is at the indie dev using GPT to make their dream game, but not at the startup selling trash to VCs at scale.&lt;/p&gt;
&lt;p&gt;Because they&apos;re easier to reach than the people we&apos;re actually mad at.
And so we end up blaming people trying to build, learn, and share while letting the ones ruining industries go untouched.&lt;/p&gt;
&lt;p&gt;I&apos;m not saying you&apos;re doing this. I don&apos;t know you.&lt;br /&gt;
But there is a dangerous pattern and we need to be careful about what incentives we&apos;re indirectly promoting.&lt;/p&gt;
&lt;p&gt;Because right now? You can&apos;t use AI, I&apos;ll ruin your reputation and drain your passion. Unless you&apos;re the CEO in which case I&apos;m still mad at you but I can&apos;t do anything about it so I&apos;ll go be angry at another indie.&lt;/p&gt;
&lt;p&gt;It&apos;s annoying. We have this purism mentality that is harming everyone.&lt;/p&gt;
&lt;p&gt;Look, I&apos;m only a single person so I&apos;m sure to some of you I&apos;ve missed the whole point. Regardless, I&apos;d love to hear other people&apos;s perspectives so if you want to discuss this you can leave your torches here too and find me over at &lt;a href=&quot;https://discord.gg/t7gNX8Kp72&quot;&gt;my discord server&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;psst, &lt;a href=&quot;https://ko-fi.com/todaymare&quot;&gt;can you consider tipping me on ko-fi&lt;/a&gt;? thank you!&lt;/p&gt;
</content></entry><entry><title>No, LLVM can&apos;t fix your code</title><id>https://daymare.net/blogs/no-llvm-cant-fix-your-code</id><updated>2025-11-23T14:21:13.916512177+00:00</updated><author><name>daymare</name><uri>https://daymare.net/</uri></author><category term="BLOGS"/><link href="https://daymare.net/blogs/no-llvm-cant-fix-your-code" rel="alternate"/><published>2025-11-23T14:21:13.916512177+00:00</published><summary>&lt;p&gt;Y&apos;all mind if I rant a bit first, promise I&apos;ll talk about a few optimizations that got my RISC-V emulator to run at 550 million instructions per second purely interpreted.&lt;/p&gt;</summary><content type="html">&lt;p&gt;Y&apos;all mind if I rant a bit first, promise I&apos;ll talk about a few optimizations that got my RISC-V emulator to run at 550 million instructions per second purely interpreted.&lt;/p&gt;
&lt;p&gt;This is sort of a follow-up to &lt;a href=&quot;../speedrunning-a-cpu/&quot;&gt;Speedrunning a CPU&lt;/a&gt;. But before I talk about what I did to make it run that fast&lt;/p&gt;
&lt;p&gt;Let me set the scene, alright.&lt;/p&gt;
&lt;p&gt;2023, I&apos;m deep in the compiler-development scene (&lt;a href=&quot;../four-years-five-failures-one-compiler/&quot;&gt;see: Four Years, Five Failures, One Compiler&lt;/a&gt;) and this is about the time where I discovered arenas and StringMaps.&lt;/p&gt;
&lt;p&gt;An Arena, at its core, is a big vector of bytes. You can put any data you want in there. If I wanted to store a &lt;code&gt;Foo&lt;/code&gt;, I&apos;d just write it into the buffer and bump a pointer by &lt;code&gt;sizeof(Foo)&lt;/code&gt;. Basically a very fast way to allocate data (like &lt;code&gt;malloc&lt;/code&gt;), but without the ability to free anything individually afterwards.&lt;/p&gt;
&lt;p&gt;This is useful in compilers since you have a lot of data to allocate and more often than not the drawback doesn&apos;t matter since you won&apos;t be releasing memory in individual bits but in phases (think lexing phase, parsing phase, semantic analysis phase, etc.).&lt;/p&gt;
&lt;p&gt;Then there&apos;s the StringMap, which is a HashMap + a vector of strings. You have a HashMap of String to an index into a vector, and then a bunch of Strings.&lt;/p&gt;
&lt;p&gt;With this you can convert all strings in the program (identifiers, actual strings, etc.) to an integer index which not only has a smaller payload but also comes with constant time comparison. (think it&apos;s actually called string interning?)&lt;/p&gt;
&lt;p&gt;These kinds of data-structure decisions are what I&apos;d call macro optimizations. The ones where you really can&apos;t expect LLVM to help you with, but of course that doesn&apos;t stop people from believing LLVM will.&lt;/p&gt;
&lt;p&gt;Anyways back to the rant, I was having a conversation with someone who was making their own programming language as well. He was trying to improve his compilers performance but he was tunnel-visioned on micro-optimizations.&lt;/p&gt;
&lt;p&gt;Look I love to give my opinion on things without being asked as much as the next guy, but this time I was asked. And considering I&apos;d just spent weeks squeezing out huge gains from StringMaps alone, I naturally suggested he give it a try. That&apos;s when he hit me with:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;LLVM can already handle those with &lt;code&gt;mem2reg&lt;/code&gt; and stuff, so why would I complicate my code?&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;I can&apos;t make a deep breath sound in a blog post but just imagine one here. The fact that I still remember this quote probably says a lot about how deeply it shook me. Not to mention that this was coming from someone making compilers, someone who presumably knows more about what a compiler backend can do than the average dev.&lt;/p&gt;
&lt;p&gt;Just to be clear: no, LLVM can&apos;t and won&apos;t magically turn the thousands of generic containers and redundant string copies into a cache-friendly arena-backed interning engine.&lt;/p&gt;
&lt;p&gt;But, while that is a conversation that stuck with me, I think (I hope) that most of us know that LLVM can&apos;t do these macro-optimizations that drastically change the data layout of the program.&lt;/p&gt;
&lt;p&gt;There is, however, a category of optimizations I&apos;d expect LLVM to be able to do&lt;/p&gt;
&lt;h2&gt;Micro-optimizations&lt;/h2&gt;
&lt;p&gt;Let&apos;s jump to the end of last week, optimizing the RISC-V emulator portion of the story.&lt;/p&gt;
&lt;p&gt;Micro-optimizations include the aforementioned &lt;code&gt;mem2reg&lt;/code&gt;, loop unrolling, constant folding (converting stuff like &lt;code&gt;3 * 4&lt;/code&gt; to &lt;code&gt;12&lt;/code&gt; at compile time), etc. the stuff that is very localized and would be tedious to do by hand. Not much reason to talk about the stuff that LLVM already handles so let&apos;s zoom in on one micro-optimization that needed my help.&lt;/p&gt;
&lt;p&gt;But before that I&apos;d like to give an honourable mention to likely/unlikely path hints.&lt;/p&gt;
&lt;p&gt;These fellas gave me about 100MIPS~ of performance in the emulator and solve a particular kind of information problem: LLVM doesn&apos;t know what your data is, so it needs to assume all branches are equally likely to be taken if it can&apos;t infer otherwise.&lt;/p&gt;
&lt;p&gt;This is a problem because it messes with the branch predictor and pollutes the instruction cache. But if you know something about your data, like that &lt;code&gt;time_elapsed &amp;gt; 5s&lt;/code&gt; almost never happens, you can tell LLVM to put that in the &apos;cold&apos; section so the CPU doesn&apos;t even think about it. Which makes the hot path possibly way faster, and also makes the cold path way slower but that&apos;s a tradeoff we can choose to make.&lt;/p&gt;
&lt;p&gt;Now let&apos;s actually talk about the optimization that gave me more than 75MIPS.&lt;/p&gt;
&lt;p&gt;No, it&apos;s not removing bounds checks. I think I still have those in the emulator, those really matter less than you&apos;d think (hey cold paths!).&lt;/p&gt;
&lt;p&gt;It&apos;s actually just deleting 5 letters.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-rs&quot;&gt;self.cycle += 1;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;into&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-rs&quot;&gt;cycle += 1;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;uh, so what did that change do exactly? Well I&apos;m glad you asked, me from 10 seconds ago! It moved the &lt;code&gt;cycle&lt;/code&gt; from stack memory into a CPU register.&lt;/p&gt;
&lt;p&gt;Okay well, technically the code change was a bit more than that because LLVM can move things in memory into a register (hey &lt;code&gt;mem2reg&lt;/code&gt; is back!).&lt;/p&gt;
&lt;p&gt;That is as long as the variable isn&apos;t passed mutably into a non-inlined function. Otherwise LLVM would have to assume it could be modified¹ and write it into memory before calling the function and at that point oftentimes LLVM just prefers not to put it into a register.&lt;/p&gt;
&lt;p&gt;In this context, the cycle counter is incremented every single instruction and isn&apos;t really passed into a lot of functions so I was really expecting this change to do basically nothing. Especially considering I had to put something like:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-rs&quot;&gt;if unlikely(cycle % 128 == 0) {
    self.cycle = cycle;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;in the main loop.&lt;/p&gt;
&lt;h2&gt;The end&lt;/h2&gt;
&lt;p&gt;Well, that&apos;s it. The moral of the story is that LLVM isn&apos;t sentient yet. Maybe when we solve the halting problem.&lt;/p&gt;
&lt;p&gt;But until then, hope to see you &lt;a href=&quot;https://discord.gg/t7gNX8Kp72&quot;&gt;in my discord server&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;If you enjoyed this, &lt;a href=&quot;https://ko-fi.com/todaymare&quot;&gt;considering dropping me a coffee&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Either way, I&apos;ll be here as usual next Sunday.&lt;/p&gt;
&lt;p&gt;¹: Yes, LLVM does fancy things like function specialization, inline heuristics, and other stuff. This is still a gross oversimplification, but I have no interest in doing a deep dive into all of that nor am I in any way qualified to.&lt;/p&gt;
</content></entry><entry><title>Speedrunning a CPU: RISC-V in a Week</title><id>https://daymare.net/blogs/speedrunning-a-cpu</id><updated>2025-11-16T00:07:52.323736212+00:00</updated><author><name>daymare</name><uri>https://daymare.net/</uri></author><category term="BLOGS"/><link href="https://daymare.net/blogs/speedrunning-a-cpu" rel="alternate"/><published>2025-11-16T00:07:52.323736212+00:00</published><summary>&lt;p&gt;I made a RISC-V Emulator that runs at 550 million instructions per second from scratch in one week. Here&apos;s how it went.&lt;/p&gt;</summary><content type="html">&lt;p&gt;I made a RISC-V Emulator that runs at 550 million instructions per second from scratch in one week. Here&apos;s how it went.&lt;/p&gt;
&lt;p&gt;So, here&apos;s the thing. After the &lt;a href=&quot;../voxel-engine-in-a-weekend/&quot;&gt;Voxel Engine in a Weekend&lt;/a&gt; project, I was looking for a new challenge. That&apos;s when I thought, huh, why not make an OS? And a compiler running in that OS, and also a browser, a neovim clone inside that OS, and while we&apos;re at it GTA 6 as well.&lt;/p&gt;
&lt;p&gt;But of course I couldn&apos;t just learn to make an OS, nah that&apos;d be too easy. And I quote:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Why stop at an OS, why not make your own CPU that runs your own OS with your own language that compiles to it.&lt;br /&gt;
&lt;em&gt;08/11/2025&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;I had to start from the CPU itself, otherwise how could I be proud of it. And I also had to do it fast, considering for this entire project (the OS, browser, compiler, neovim clone, GTA 6) I allocated myself a generous singular year. It was set, I was going to speedrun making a CPU, and I was going to use RISC-V.&lt;/p&gt;
&lt;h2&gt;Step 1: Say you&apos;ll make an assembler too!&lt;/h2&gt;
&lt;p&gt;Okay I hear you, I just said &amp;quot;gotta go fast&amp;quot; and now am considering making an assembler for seemingly no reason. In my defence, I had not a single clue where to start, but I have made &lt;a href=&quot;../four-years-five-failures-one-compiler/&quot;&gt;plenty of programming languages&lt;/a&gt; so I thought: okay, if we&apos;re going to do this I might as well start with an assembler, since it&apos;ll be a nice easy step to smooth out the curve and get me used to the binary format of RISC-V.&lt;/p&gt;
&lt;p&gt;In theory, an assembler is as simple as it gets. You have really simple syntax, maybe a few shapes of instructions, basically no semantic analysis, and a trivial 1:1 code generation phase. In practice, however, GOOD assemblers are filled with macros, pseudoinstructions, constant folding, and probably so much more that I can&apos;t think of off the top of my head.&lt;/p&gt;
&lt;p&gt;Are any of these really hard? Not really. But all of them require a lot more knowledge in the target instruction set than I had at the start of this week. Pseudoinstructions aren&apos;t officially documented... what the hell is a macro... constant folding who? You can probably guess that my hope of writing an assembler vanished VERY quickly, and all of that before I even got to the codegen part of the assembler. You know, the part that&apos;s actually useful to me making an emulator?&lt;/p&gt;
&lt;p&gt;I could&apos;ve made a toy assembler, but would I have used it? Absolutely not. This thing is hard enough already I don&apos;t need one more thing to debug. So I made the very difficult and heart breaking call to not make an assembler.&lt;/p&gt;
&lt;h2&gt;Step 2: Oh it&apos;s only 200 pages-&lt;/h2&gt;
&lt;p&gt;Now, time for the emulator. I wanted to, I needed to keep this emulator as simple as possible so I could complete it in this week. So I decided, no virtual memory, no fancy extensions, no jit, nothing fancy. And then it was Sunday morning.&lt;/p&gt;
&lt;p&gt;I opened up my computer with the hopes to get the emulator started. See, the first thing I do with any interpreter I make is to run the recursive-Fibonacci algorithm. It&apos;s a nice little stress test that is very easy to run and gets the dopamine flowing you know? So I went to the RISC-V site to check out the instruction set.&lt;/p&gt;
&lt;p&gt;There were 2 specs. &lt;a href=&quot;https://docs.riscv.org/reference/isa/_attachments/riscv-unprivileged.pdf&quot;&gt;Volume 1&lt;/a&gt; for unprivileged architecture, and &lt;a href=&quot;https://docs.riscv.org/reference/isa/_attachments/riscv-privileged.pdf&quot;&gt;Volume 2&lt;/a&gt; for privileged. Since my goal was to make an OS and not to compile to a RISC-V user platform I thought: okay, I just need Volume 2. I opened it up and it had about 221 pages. Okay that&apos;s a lot of pages. Hell, that&apos;s 6 pages of just table of contents.&lt;/p&gt;
&lt;p&gt;But it&apos;s managable, I could skim through it in a day or so and then reference it back or something. So I got looking. Huh. I can&apos;t see where the instructions are.&lt;/p&gt;
&lt;p&gt;Here&apos;s the thing, dear reader. You might&apos;ve noticed that the books are titled Volume 1 &amp;amp; 2, and while I didn&apos;t think they actually meant it in that way they absolutely did. The instructions were on Volume 1 as well.&lt;/p&gt;
&lt;p&gt;Would you like to guess how long Volume 1 is?&lt;br /&gt;
About as long as Volume 2?
Yeah that&apos;s about what I&apos;d expect too,&lt;/p&gt;
&lt;p&gt;NOT 727 PAGES.&lt;/p&gt;
&lt;p&gt;I ain&apos;t reading all that. If you&apos;re curious that&apos;s 19 pages of table of contents.&lt;/p&gt;
&lt;p&gt;Granted the spec is very well structured and looking back all I would&apos;ve had to check out was maybe about 20-30 pages filled with diagrams. But &apos;727 pages&apos; was enough to make me close the tab and look for other more concise sheets. And that&apos;s when I found it. My saviour, &lt;a href=&quot;https://book.rvemu.app/index.html&quot;&gt;Writing a RISC-V Emulator in Rust&lt;/a&gt;. When I saw this I went&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Omg, I don&apos;t need to learn anything let&apos;s go!&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;..that is, until I clicked on the link. Here, I&apos;ll show you the table of contents of it at the time of writing:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Writing a RISC-V Emulator in Rust
   1. Hardware Components
      1.1. CPU with Two Instructions
      1.2. Memory and System Bus
      1.3. Control and Status Registers
   2. Instruction Set
      2.1. RV64I Base Integer Instruction Set
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;And, that&apos;s it. I&apos;m not trying to bash on the creator of it I&apos;m sure it&apos;s either an unfinished or even an abandoned passion project, but when I saw this the timing was so unfortunate it became funny. For those curious, that&apos;s about 0.1 pages of table of contents.&lt;/p&gt;
&lt;p&gt;BUT THEN IT APPEARED!&lt;/p&gt;
&lt;p&gt;My actual saviour,&lt;br /&gt;
the &lt;a href=&quot;https://msyksphinz-self.github.io/riscv-isadoc/&quot;&gt;RISC-V Instruction Set Specifications&lt;/a&gt; by some random github person.&lt;/p&gt;
&lt;p&gt;It was pretty, it was useful, it sometimes had typoes and mistakes, but most importantly, it had whitespace so it didn&apos;t look scary.&lt;/p&gt;
&lt;h2&gt;Step 3: ..make the thing already?&lt;/h2&gt;
&lt;p&gt;It was still Sunday. I got some very basic RISC-V code working.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;    .section .text
    .globl _start
_start:
    li   a0, 42
    li   a1, 99
    add  a2, a0, a1
1:  j   1b                # infinite loop
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;can you guess what it does? Probably not, it&apos;s too advanced.&lt;/p&gt;
&lt;p&gt;But now that I had this very simple snippet working the momentum curve was slowly tilting in my favour.&lt;/p&gt;
&lt;p&gt;By the end of Sunday I had 13 instructions ready. And fast forward a few days I had the entirety of RISCV64I implemented. Which of course surfaced a new kind of problem. The hardest, least motivating part of writing an emulator like this is that you really have no idea if your implementation is correct. Sure yeah your programs might be working but you don&apos;t know the edge-cases, and when a program doesn&apos;t work like you expected good luck trying to debug it.&lt;/p&gt;
&lt;p&gt;When I brought this point up to one of my friends he linked me the &lt;a href=&quot;https://github.com/riscv-software-src/riscv-tests&quot;&gt;RISC-V Test Suite&lt;/a&gt;, which I promptly ignored in favour of compiling some Rust code and running it on my emulator.&lt;/p&gt;
&lt;p&gt;..damn, all that set up for nothing.&lt;/p&gt;
&lt;p&gt;Okay, to compile Rust to RISC-V, Rust provides a bunch of pre-made targets. But you see, RISC-V at it&apos;s core is a very simple instruction set with 47 instructions and it provides optional extensions like multiplcation, floating-point numbers, atomics, etc. And not implementing these optional extensions is really not an option with Rust&apos;s premade targets.&lt;/p&gt;
&lt;p&gt;So I had to make my own custom Rust target. Which sounds interesting but was mostly done by my friend and when I asked him how he did it his response was &amp;quot;I asked ChatGPT and copy pasted the errors until it worked&amp;quot;. Which, fair enough.&lt;/p&gt;
&lt;p&gt;Of course, the first thing I compiled with Rust was&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-rs&quot;&gt;fn fib(n: usize) -&amp;gt; usize {
    if n &amp;lt;= 1 { n }
    else { fib(n-1) + fib(n-2) }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Which worked first try of course, on the 4th try.&lt;/p&gt;
&lt;p&gt;And now that Fibonacci was working I had to go for the second milestone of interpreter development: Flappy Bird.&lt;/p&gt;
&lt;h2&gt;Step 4: So how do we see?&lt;/h2&gt;
&lt;p&gt;Well, first things first, we need a window.&lt;/p&gt;
&lt;p&gt;Since this is my emulator, I get to make the rules. And I decided that a specific memory range would be dedicated to the framebuffer. One of the few places where I actually went with the simplest option yes.
So at address &lt;code&gt;0x1000_0000&lt;/code&gt;, I placed a 1920x1080 RGBA framebuffer, so I could see the 16x16 pixel art of Flappy Bird in full HD.&lt;/p&gt;
&lt;p&gt;Needless to say I got humbled FAST.&lt;/p&gt;
&lt;p&gt;See at this point, my emulator was running at a solid 100 million instructions per second (MIPS!, which sounds impressive until you do the math.&lt;/p&gt;
&lt;p&gt;A 1920x1080 framebuffer has over 2 million pixels.
Even if we only spent 5 instructions per pixel (which is practically impossible), that&apos;s already 10 million instructions per frame, or 10FPS.&lt;/p&gt;
&lt;p&gt;That&apos;s with math that&apos;s about as accurate as rounding PI to 10.&lt;/p&gt;
&lt;p&gt;I know that I&apos;m &amp;quot;speedrunning a CPU&amp;quot; but like.. it has to be at least usable you know?&lt;/p&gt;
&lt;p&gt;So in a moment of weakness, in the middle of a uni lecture, I lowered the resolution to 640x360. Which brought the FPS from a theoretical 10FPS to about 40 real FPS.&lt;br /&gt;
For drawing a white background.&lt;/p&gt;
&lt;p&gt;But 40 FPS ain&apos;t real time. That&apos;s AI-interpolated horrible animation framerate, not a usable GUI app framerate. So I got to optimizing, and the one thing that really improved the performance was the Instruction Cache.&lt;/p&gt;
&lt;h3&gt;The Instruction Cache&lt;/h3&gt;
&lt;p&gt;Before I explain that, let&apos;s look at the problem it solves first:&lt;/p&gt;
&lt;p&gt;When decoding a RISC-V instruction, you don&apos;t really have a single number that tells you what the instruction does. Instead, you have these instruction shapes (like register-register, register-immediate, jump instructions) and only after you figure out the instruction shape you can decode the instruction itself.&lt;/p&gt;
&lt;p&gt;For hardware, this is extremely fast. Since the main decoder only needs to know the shape of the instruction and then can send it to something that knows how to decode it.&lt;br /&gt;
For software, this is absolutely horrible.&lt;/p&gt;
&lt;p&gt;For software, we first need to match on the shape of the instruction and then match on the actual instruction in order to execute it. But if you look like 5 lines above, you&apos;ll know that there&apos;s a solution to this. We can just cache the decoded instruction instead of decoding it on the fly every time. Let me introduce you to: The Instruction Cache&lt;/p&gt;
&lt;p&gt;Basically, when were trying to decode an instruction we first hit up the instruction cache to see if it has already been decoded. If yes, then just execute that. If no, then decode it and put it in the instruction cache, then run it.&lt;/p&gt;
&lt;p&gt;But how are we going to decide what to decode?&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;We could have a 1:1 mapping of our entire memory to instructions&lt;br /&gt;
our entire 64 bits of memory..?&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Can&apos;t we just decode the code given to the emulator?&lt;br /&gt;
Well no. If we want to run actual OS code, it&apos;ll need to be able to load new programs at runtime&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;What about a HashMap?&lt;br /&gt;
A HashMap lookup every single instruction would probably reduce our 40FPS to 0.4&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;So what did I do?&lt;br /&gt;
Pages!&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;When loading a PC we can look at what page it&apos;s in and load that page to the instruction cache. And that works! But (not so) surprisingly, when implemented naively it&apos;s not much faster than just decoding on the fly.&lt;/p&gt;
&lt;p&gt;The reason for this is that in the naive solution, we&apos;re checking the PC every time it moves to the next instruction, which is horribly slow and unnecessary considering most of the time we&apos;re just moving to the next instruction.&lt;/p&gt;
&lt;p&gt;But if we&apos;re not checking the PC every time, how do we know that it has changed?&lt;/p&gt;
&lt;p&gt;Here&apos;s the idea, let&apos;s say our pages are 1024 instructions (4KBs).&lt;br /&gt;
What if we inserted a fake instruction at the end of the page that tells us that we&apos;re at the end of the page. Kind of like a nul-terminated string!&lt;/p&gt;
&lt;p&gt;Then we can go fetch the new page our PC is in.&lt;/p&gt;
&lt;p&gt;And then for instructions that modify the PC directly (jumps, branches, etc.) we can just always fetch the page for the PC.&lt;/p&gt;
&lt;p&gt;Of course I did add early-outs to that page fetch for the common case where the jump is in the same page but that&apos;s the general idea of the Instruction Cache.&lt;/p&gt;
&lt;p&gt;After tinkering around with the Instruction Cache a bit more, instruction decoding became essentially free. Which is absolutely amazing, but I was still at only 65FPS.&lt;/p&gt;
&lt;h3&gt;The M extension&lt;/h3&gt;
&lt;p&gt;Next thing I implemented was support for more advanced operations. Such as multiplication (scary), and division!&lt;/p&gt;
&lt;p&gt;This brought the performance up from 65FPS to about 80. Mostly because before this we were using a software implementation of division and multiplication.&lt;/p&gt;
&lt;p&gt;Which means hundreds of instructions that got replaced with a single instruction.&lt;/p&gt;
&lt;p&gt;And unfortunately I was out of ideas for how to make this any faster, so I had to accept that my emulator was only at 160MIPS, which is still amazing for a pure interpreter!&lt;br /&gt;
If I had that many MIPS back in the year 200 I would&apos;ve been basically god.&lt;/p&gt;
&lt;p&gt;This is about the time where I went back to the RISC-V test suite I mentioned a bit ago.&lt;/p&gt;
&lt;h2&gt;Step 5) Timeout&lt;/h2&gt;
&lt;p&gt;Originally, this section was going to be about how, on the very last day (the saturday right before this post went up) &lt;a href=&quot;https://www.youtube.com/@leddoo&quot;&gt;leddoo&lt;/a&gt;, completely unprompted, found a way to bump the interpreter from 160 MIPS to 540 MIPS. Purely interpreted.&lt;/p&gt;
&lt;p&gt;And I was gonna talk about it.&lt;br /&gt;
I really was.&lt;/p&gt;
&lt;p&gt;But then I wrote the title &amp;quot;Timeout&amp;quot; and realized:
crap, I forgot to mention timers.&lt;/p&gt;
&lt;p&gt;And listen-&lt;br /&gt;
I would very much rather not talk about whatever that was.&lt;br /&gt;
It apparently involved something called CSRs??&lt;/p&gt;
&lt;p&gt;So that&apos;s it. Just like that my one-week CPU speedrun was over.&lt;br /&gt;
That&apos;s your ending. We&apos;re done here.&lt;/p&gt;
&lt;p&gt;...&lt;/p&gt;
&lt;p&gt;oh&lt;br /&gt;
What&apos;s that?&lt;br /&gt;
You&apos;re curious how we went from 160MIPS to 550?&lt;/p&gt;
&lt;p&gt;Well that&apos;s awkward.&lt;br /&gt;
Honestly, I&apos;d love to explain it. But it&apos;s 11:30PM, I haven&apos;t even made a thumbnail yet, and this post goes up at 9AM.
Maybe I&apos;ll write about it in another post if enough people ask. For now, &lt;a href=&quot;https://github.com/todaymare/riscv-emulator&quot;&gt;here&apos;s the repo&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Here, I&apos;ll even make it easy to bother me:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://discord.gg/t7gNX8Kp72&quot;&gt;My discord server&lt;/a&gt;, if you want to bother me about the deets this is probably the best place.&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;mailto:contact@daymare.net&quot;&gt;My E-Mail&lt;/a&gt;, probably not the best place but I do enjoy getting mails a lot. Makes me feel important&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;And hey, if you enjoyed this post consider &lt;a href=&quot;https://ko-fi.com/todaymare&quot;&gt;buying me a cup of coffee&lt;/a&gt;.&lt;br /&gt;
I kinda forgot this was a &amp;quot;weekly blog&amp;quot; and got way too deep into emulator dev.&lt;br /&gt;
It happens.&lt;/p&gt;
&lt;p&gt;Alright. Goodnight.&lt;/p&gt;
</content></entry><entry><title>Voxel Engine in a Weekend</title><id>https://daymare.net/blogs/voxel-engine-in-a-weekend</id><updated>2025-11-08T08:59:03.535128220+00:00</updated><author><name>daymare</name><uri>https://daymare.net/</uri></author><category term="BLOGS"/><link href="https://daymare.net/blogs/voxel-engine-in-a-weekend" rel="alternate"/><published>2025-11-08T08:59:03.535128220+00:00</published><summary>&lt;p&gt;It feels like everyone who learns how to make a voxel-engine learns it through sheer osmosis of information, so let&apos;s change that. Come along, let&apos;s make you a voxel engine!&lt;/p&gt;</summary><content type="html">&lt;p&gt;It feels like everyone who learns how to make a voxel-engine learns it through sheer osmosis of information, so let&apos;s change that. Come along, let&apos;s make you a voxel engine!&lt;/p&gt;
&lt;h2&gt;Overview&lt;/h2&gt;
&lt;p&gt;Hello, are you ready? Because we&apos;re going to take you from a single cube to an entire voxel engine!&lt;br /&gt;
Here&apos;s what you&apos;ll need to bring:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Your own language&lt;/strong&gt;! All the code examples are in pseudocode so unless you write a compiler for it you&apos;re not going to be able to copy paste it anyways, so pick something you like.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Your own rendering API&lt;/strong&gt;! Pick whatever you want, this isn&apos;t a rendering tutorial.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;If you have any questions during the process feel free to reach out to me on &lt;a href=&quot;https://discord.gg/t7gNX8Kp72&quot;&gt;my discord server&lt;/a&gt; or contact me at &lt;a href=&quot;mailto:contact@daymare.net&quot;&gt;contact@daymare.net&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;If you&apos;re set, grab a drink and let&apos;s do this!&lt;/p&gt;
&lt;h2&gt;Maybe this should&apos;ve been before Overview&lt;/h2&gt;
&lt;p&gt;Here&apos;s the contents of this post since I assume it&apos;ll be quite long.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;#hello-cube&quot;&gt;Hello, cube&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#meshes-and-chunks&quot;&gt;Meshes and Chunks&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#world-generation&quot;&gt;World Generation&lt;/a&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;#voxel-types&quot;&gt;Voxel Types&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#physics&quot;&gt;Physics&lt;/a&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;#raycasting&quot;&gt;Raycasting&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#aabb-collision&quot;&gt;AABB Collision&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#one-last-thing&quot;&gt;One last thing..&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#the-end&quot;&gt;The End?&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#or-is-it&quot;&gt;..or is it?&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Hello, cube&lt;/h2&gt;
&lt;p&gt;I&apos;m not planning on making the rest of the post so undetailed but rendering a cube is mostly a graphics task and if you&apos;re using OpenGL you only need to read 6 pages from &lt;a href=&quot;https://learnopengl.com/Getting-started/Creating-a-window&quot;&gt;LearnOpenGL&lt;/a&gt; to get to this point. So I&apos;ll assume you can handle this on your own.&lt;/p&gt;
&lt;p&gt;By the end of it you should roughly have a function &lt;code&gt;draw_cube&lt;/code&gt; that takes in a &lt;code&gt;Vec3&lt;/code&gt; position and draws a cube there.
Here&apos;s mine:
&lt;img src=&quot;https://daymare.net/blogs/voxel-engine-in-a-weekend/assets/draw-a-cube.png&quot; alt=&quot;A cube&quot; /&gt;&lt;/p&gt;
&lt;p&gt;And now, all that&apos;s left to do is place more cubes everywhere! A simple for loop should suffice&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-rs&quot;&gt;for z in 0..32 {
    for x in 0..32 {
        draw_cube(Vec3::new(x, 0, z));
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This should give us a one voxel tall 32x32 rectangle.
&lt;img src=&quot;https://daymare.net/blogs/voxel-engine-in-a-weekend/assets/32x32-platform.png&quot; alt=&quot;32x32 Platform&quot; /&gt;&lt;/p&gt;
&lt;p&gt;Now, instead of doing the for loop every time, let&apos;s put our voxels in a data structure and call it our &lt;code&gt;World&lt;/code&gt;.&lt;br /&gt;
We can put a HashSet of positions here to know what world coordinates have a voxel.&lt;/p&gt;
&lt;p&gt;Now depending on what programming language you&apos;re using you might not be able to put &lt;code&gt;Vec3&lt;/code&gt; in a HashSet.
And even if you can, we should still be converting our float-based &lt;code&gt;Vec3&lt;/code&gt; into an integer-based &lt;code&gt;IVec3&lt;/code&gt; as that&apos;ll allow us to ensure that voxels are aligned to a grid. Which is important for foreshadowing reasons.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-rs&quot;&gt;struct World {
    voxels: HashSet&amp;lt;IVec3&amp;gt;,
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;then, all we need to do is populate our world at the start&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-rs&quot;&gt;for z in 0..32 {
    for x in 0..32 {
        world.voxels.insert(IVec3::new(x, 0, z))
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;and then every frame we can render our world like so&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-rs&quot;&gt;for voxel_pos in world.voxels {
    draw_cube(voxel_pos);
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;After all that you should have.. the exact same scene! But now we can do funky stuff like adding a random cube in the middle of nowhere by just adding it to our &lt;code&gt;World&lt;/code&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-rs&quot;&gt;world.voxels.insert(IVec3::new(7, 2, 13))
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;img src=&quot;https://daymare.net/blogs/voxel-engine-in-a-weekend/assets/random-block.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;We can even add and remove blocks at runtime. Let&apos;s make it so when we press &lt;code&gt;Space&lt;/code&gt; the block we&apos;re standing in is created if it doesn&apos;t exist and removed if it does&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-rs&quot;&gt;if input.is_key_pressed(Key::Space) {
    var position = camera.position.as_ivec3();
    if world.voxels.exists(position) {
        world.voxels.remove(position);
    } else {
        world.voxels.insert(camera.position.as_ivec3())
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Now I encourage you to go experiment with this a bit; maybe change the dimensions of the initial platform, add weird structures etc. before continuing on.&lt;/p&gt;
&lt;h2&gt;Meshes and Chunks&lt;/h2&gt;
&lt;p&gt;If you&apos;ve tried to increase the platform&apos;s size you might&apos;ve noticed that the performance suffers greatly. This is because if you implemented the &lt;code&gt;draw_cube&lt;/code&gt; function as naively as I have (without instancing) you&apos;ll be uploading &amp;amp; drawing &lt;code&gt;36*voxel_count&lt;/code&gt; vertices every frame. Which is a lot of data to be uploading to the GPU every frame.&lt;/p&gt;
&lt;p&gt;What we can do is create a single Mesh for our world and only upload that once at the start.
To do that we&apos;ll first need to add a Mesh to our world&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-rs&quot;&gt;struct World {
    voxels: HashSet&amp;lt;IVec3&amp;gt;,
    mesh: Mesh?,
}
&lt;/code&gt;&lt;/pre&gt;
&lt;blockquote&gt;
&lt;p&gt;The &lt;code&gt;?&lt;/code&gt; means that the Mesh can be none, this is because we won&apos;t have a Mesh at the start of the program.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;and then when rendering we will&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-rs&quot;&gt;if world.mesh != none {
    draw_mesh(world.mesh);
} else {
    create_mesh(world);
    draw_mesh(world.mesh);
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;and &lt;code&gt;create_mesh&lt;/code&gt; would look something like&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-rs&quot;&gt;fn create_mesh(world: &amp;amp;World) {
    var vertices = [];

    for voxel_pos in world.voxels {
        draw_cube(vertices, voxel_pos);
    }

    world.mesh = Mesh::from_vertices(vertices);
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;so now if you run it you should once again get the exact same scene. But! you might have noticed that now you can&apos;t modify the world anymore.&lt;/p&gt;
&lt;p&gt;Ah, the joys of engine development. What we have done is cache our world Mesh on the first frame, but currently we don&apos;t have a way of invalidating it.&lt;br /&gt;
Which means even after we modify our HashSet we will be drawing the cached Mesh from the first frame.&lt;/p&gt;
&lt;p&gt;Sounds intimidating but all we need to do is go back to our world modification code and set &lt;code&gt;world.mesh&lt;/code&gt; to none&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-rs&quot;&gt;if input.is_key_pressed(Key::Space) {
    world.mesh = none;
    ...
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Voilà!&lt;/p&gt;
&lt;p&gt;Now we finally have the same thing we had 5 minutes ago.&lt;/p&gt;
&lt;p&gt;Before we were drawing the entire world block by block, now we are drawing the entire world at once which comes with a brand new problem of its own, we need to rebuild the entire world whenever a single block changes.&lt;/p&gt;
&lt;p&gt;Which sounds fine when our world is 32x32 but at something like 2048x2048? That&apos;s over four million cubes to rebuild just because one changed.&lt;/p&gt;
&lt;p&gt;This leads to the second buzzword of this section: Chunks&lt;/p&gt;
&lt;p&gt;But before we move onto that let&apos;s introduce a &lt;code&gt;flip_voxel&lt;/code&gt; function on our World to make our job a bit more convenient.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-rs&quot;&gt;fn flip_voxel(world: &amp;amp;World, position: IVec3) {
    world.mesh = none;
    if world.voxels.contains(position) {
        world.voxels.remove(position);
    } else {
        world.voxels.insert(position.as_ivec3())
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;and then we can switch out our input to&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-rs&quot;&gt;if input.is_key_pressed(Key::Space) {
    flip_voxel(world, camera.position);
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Basically, instead of treating the entire world as one giant data structure we split it up into smaller pieces called chunks.&lt;br /&gt;
Each chunk is just a mini-world, let&apos;s say 32x32x32 blocks big (the exact size doesn&apos;t really matter)&lt;/p&gt;
&lt;p&gt;When you place or remove a block, you don&apos;t rebuild the whole world but instead just rebuild the chunk that the block is in.&lt;/p&gt;
&lt;p&gt;Basically, we&apos;re building the world out of a bunch of tiny worlds that all work independently, which allows us to change one of them without changing all the others.&lt;/p&gt;
&lt;p&gt;[a 2d image that shows a world being a single chunk vs split up into a 32x32 chunks]&lt;/p&gt;
&lt;p&gt;All we need to do for implementing it is modify our World so that instead of storing every voxel in one big HashSet we&apos;ll group them by which chunk they belong to. Each chunk keeps track of its own voxels, and the world just tracks which chunks exist. We will also remove the mesh field from our world and move it to its chunk.&lt;/p&gt;
&lt;p&gt;For all practical purposes we&apos;re just renaming our World into Chunk and creating a new World type.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-rs&quot;&gt;struct World {
    chunks: HashMap&amp;lt;IVec3, Chunk&amp;gt;, // !!
}

struct Chunk {
    voxels: HashSet&amp;lt;IVec3&amp;gt;,
    mesh: Mesh?,                   // !!
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;For my implementation I&apos;ll say that a Chunk will start on (0, 0, 0) and end at (32, 32, 32). And our World will store our Chunks in chunk-space.&lt;/p&gt;
&lt;p&gt;That means that the Chunk at (1, 5, 3) will span voxels from (1x32..2x32, 5x32..6x32, 3x32..4x32).&lt;/p&gt;
&lt;p&gt;In order to convert from a world-position to a chunk position &amp;amp; a local position we&apos;ll need to do some math on it. We can divide our world-position by our chunk size (32) to get the position of the chunk that world position is in. Then we take the remainder (or modulus) of our world position by our chunk size (32) to find the chunk local position.&lt;/p&gt;
&lt;p&gt;However there&apos;s something subtle but important we need to be wary of, negative numbers.&lt;/p&gt;
&lt;p&gt;Take the world position (-7, 0, 0) for example, we want it to be on chunk (-1, 0, 0) with a local offset of (7, 0, 0). But in most programming languages if you just did what we talked about above you would get a chunk position of (0, 0, 0) with a local offset of (-7, 0, 0). The problem is that the chunk at (0, 0, 0) is double the size of all other chunks.&lt;/p&gt;
&lt;p&gt;That&apos;s kind of annoying, in order to solve this we can use the euclidian versions of these operations, also known as floor division/remainder.&lt;/p&gt;
&lt;p&gt;So let&apos;s look at how we&apos;ll need to change our &lt;code&gt;flip_voxel&lt;/code&gt; function&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-rs&quot;&gt;fn flip_voxel(world: &amp;amp;World, position: IVec3) {
    var chunk_position = position.div_euclid(32);
    var local_position = position.rem_euclid(32);

    var chunk = get_or_create_chunk(world, chunk_position);

    chunk.mesh = none;
    if chunk.voxels.contains(local_position) {
        chunk.voxels.remove(local_position);
    } else {
        chunk.voxels.insert(local_position.as_ivec3())
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;We&apos;ll need to create a &lt;code&gt;get_or_create_chunk&lt;/code&gt; function, this&apos;ll make more sense later&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-rs&quot;&gt;fn get_or_create_chunk(world: &amp;amp;World, chunk_position: IVec3): &amp;amp;Chunk {
    if !world.chunks.contains(chunk_position) {
        world.chunks.insert(chunk_position, Chunk { mesh: none, voxels: HashSet::new() } )
    }

    return world.chunks.get(chunk_position)
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Of course, we&apos;ll also need to remove the &lt;code&gt;create_mesh&lt;/code&gt; function for our World and make one for our chunks. Okay I lied we&apos;re just gonna change the type parameter from World to Chunk.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-rs&quot;&gt;fn create_mesh(chunk: &amp;amp;Chunk) {
    var vertices = [];

    for voxel_pos in chunk.voxels {
        draw_cube(vertices, voxel_pos);
    }

    chunk.mesh = Mesh::from_vertices(vertices);
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;and then update our rendering code. When rendering we&apos;ll need to offset the mesh by &lt;code&gt;chunk_pos x chunk_size&lt;/code&gt;. Because, yk, we&apos;re just tiling these chunks.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-rs&quot;&gt;for chunk_pos, chunk in world.chunks {
    var offset = chunk_pos * 32;
    if chunk.mesh != none {
        draw_mesh(chunk.mesh, offset);
    } else {
        create_mesh(chunk);
        draw_mesh(chunk.mesh, offset);
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;I almost forgot, but we also need to change how we populated our world at the start&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-rs&quot;&gt;for z in 0..32 {
    for x in 0..32 {
        world.flip_voxel(IVec3::new(x, 0, z));
    }
}

world.flip_voxel(IVec3::new(7, 2, 13));
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;so for, hopefully but not likely, the last time we finally have the same thing we had 15 minutes ago.
&lt;img src=&quot;https://daymare.net/blogs/voxel-engine-in-a-weekend/assets/different-angle-lmao.png&quot; alt=&quot;Different Angle&quot; /&gt;&lt;/p&gt;
&lt;p&gt;phew, well that was a lot of work for not much visual pay-off huh?&lt;br /&gt;
I should probably take you through optimizing the mesh generation but before that, how about we take a look at some basic world generation instead?&lt;/p&gt;
&lt;h2&gt;World Generation&lt;/h2&gt;
&lt;p&gt;So the simplest world-generation might work with just making a flat platform on a certain Y-level, and with the setup we have it&apos;ll be trivial!&lt;/p&gt;
&lt;p&gt;The first thing we&apos;ll need to do is modify our &lt;code&gt;get_or_create_chunk&lt;/code&gt; function (see I told you it&apos;d be important later!)&lt;/p&gt;
&lt;p&gt;So far it&apos;s been responsible for creating empty chunks when we needed them. But now we can start to use it to also generate those chunks when needed.&lt;br /&gt;
Whenever a new chunk is generated, we&apos;ll stuff it with some terrain. In this case that terrain is just &amp;quot;flat&amp;quot;.
It&apos;s not glamorous but hey, something something Rome wasn&apos;t built in a day and I did say this is going to take a weekend.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-rs&quot;&gt;fn get_or_create_chunk(world: &amp;amp;World, chunk_position: IVec3): &amp;amp;Chunk {
    if !world.chunks.contains(chunk_position) {
        
        var voxels = HashSet::new();
        if chunk_position.y == 0 {
            for z in 0..32 {
                for x in 0..32 {
                    voxels.insert(IVec3::new(x, 0, z));
                }
            }
        }

        world.chunks.insert(chunk_position, Chunk { mesh: none, voxels } )
    }

    return world.chunk.get(chunk_position)
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;If you just ran this code you might&apos;ve noticed that the world is looking kinda empty .
That&apos;s because we don&apos;t generate any chunks that aren&apos;t being modified and so to fix this we need to introduce the concept of a render distance.&lt;/p&gt;
&lt;p&gt;The idea is simple: pick a radius around the camera and touch &apos;em all.&lt;/p&gt;
&lt;p&gt;I could bore you with a dozen different ways to do this efficiently, or we can just touch everything every frame. Which is fine, probably.&lt;/p&gt;
&lt;p&gt;So let&apos;s go back to our rendering loop and tweak it.
We need to offset our radius so that it&apos;s around the camera, in order to do that we can just offset it by the chunk the camera is in.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-rs&quot;&gt;var radius = 4;

var camera_chunk = camera.position.div_euclid(32);

for y in -radius..radius {
    for z in -radius..radius {
        for x in -radius..radius {
            var chunk_offset = IVec3(x, y, z);
            var chunk_pos = camera_chunk + chunk_offset;

            var chunk = get_or_create_chunk(world, chunk_pos);
            
            var offset = chunk_pos * 32;
            if chunk.mesh != none {
                draw_mesh(chunk.mesh, offset);
            } else {
                create_mesh(chunk);
                draw_mesh(chunk.mesh, offset);
            }
        }
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;And there it is! A flat, infinite plane. Not much to look at, but hey, at least it’s not the same thing we’ve been staring at for the past few sections.&lt;br /&gt;
&lt;img src=&quot;https://daymare.net/blogs/voxel-engine-in-a-weekend/assets/flat-world-gen-with-hole.png&quot; alt=&quot;Flat World Generation with Hole&quot; /&gt;&lt;/p&gt;
&lt;p&gt;Except, do you see that giant hole too? Okay so that&apos;s our old &amp;quot;world generation&amp;quot; code sort of colliding with our new one. We can just remove that part and be fine.&lt;br /&gt;
&lt;img src=&quot;https://daymare.net/blogs/voxel-engine-in-a-weekend/assets/flat-world-gen-without-hole.png&quot; alt=&quot;Flat World Generation without Hole&quot; /&gt;&lt;/p&gt;
&lt;p&gt;I think this might be the perfect time to take a tiny detour and talk about Voxel types.&lt;/p&gt;
&lt;h3&gt;Voxel Types&lt;/h3&gt;
&lt;p&gt;So far our world has been made up of a HashSet of voxels where a voxel either exists or it doesn&apos;t, but that&apos;s kinda boring so let&apos;s give our voxels some personality.&lt;/p&gt;
&lt;p&gt;We&apos;ll start by creating an enum called &lt;code&gt;VoxelKind&lt;/code&gt;. You can obviously call it whatever you want. Bonus brownie points if you call it &lt;code&gt;WoahThisIsVeryCool&lt;/code&gt;.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-rs&quot;&gt;enum VoxelKind {
    Dirt,
    Stone
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;and then replace our HashSet with a HashMap&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-rs&quot;&gt;struct Chunk {
    voxels: HashMap&amp;lt;IVec3, VoxelKind&amp;gt;, // !!
    mesh: Mesh?,
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;We&apos;ll still treat the absence of a value as air/nothing.&lt;br /&gt;
But yeah, that&apos;s basically all we need to do in order to support multiple voxel types.&lt;br /&gt;
Apart from, you know, the dozen or so spots in our code that will now immediately break.&lt;/p&gt;
&lt;p&gt;Let&apos;s take it from the top of my file.&lt;br /&gt;
First, we need to change our &lt;code&gt;flip_voxel&lt;/code&gt; function alongside renaming it because well, you can&apos;t really flip a Dirt block can you? I&apos;ll rename it to &lt;code&gt;set_voxel&lt;/code&gt; or something.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-rs&quot;&gt;fn set_voxel(world: &amp;amp;World, position: IVec3, kind: VoxelKind?) { // !!
    var chunk_position = position.div_euclid(32);
    var local_position = position.rem_euclid(32);

    var chunk = get_or_create_chunk(world, chunk_position);

    chunk.mesh = none;
    if kind == none {                                         // !!
        chunk.voxels.remove(local_position);
    } else {
        chunk.voxels.insert(local_position, kind)    // !!
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;then our &lt;code&gt;get_or_create_chunk&lt;/code&gt; function of course&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-rs&quot;&gt;fn get_or_create_chunk(world: &amp;amp;World, chunk_position: IVec3): &amp;amp;Chunk {
    if !world.chunks.contains(chunk_position) {
        
        var voxels = HashMap::new();                            // !!
        if chunk_position.y == 0 {
            for z in 0..32 {
                for x in 0..32 {
                    voxels.insert(IVec3::new(x, 0, z), VoxelKind::Dirt); // !!
                }
            }
        }

        world.chunks.insert(chunk_position, Chunk { mesh: none, voxels } )
    }

    return world.chunk.get(chunk_position)
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;and our &lt;code&gt;create_mesh&lt;/code&gt; function as well, of course this will need to come with a change to your &lt;code&gt;draw_cube&lt;/code&gt; function but since this isn&apos;t a rendering tutorial I&apos;ll just pass in the VoxelKind and leave the colouring up to you.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-rs&quot;&gt;fn create_mesh(chunk: &amp;amp;Chunk) {
    var vertices = [];

    for (voxel_pos, voxel_kind) in chunk.voxels {    // !!
        draw_cube(vertices, voxel_pos, voxel_kind);  // !!
    }

    chunk.mesh = Mesh::from_vertices(vertices);
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;our input needs to change as well&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-rs&quot;&gt;if input.is_key_pressed(Key::Space) {
    set_voxel(world, camera.position, VoxelKind::Stone);
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;and then everything should be working!&lt;br /&gt;
&lt;img src=&quot;https://daymare.net/blogs/voxel-engine-in-a-weekend/assets/multiple-block-types.png&quot; alt=&quot;Multiple Block Types&quot; /&gt;&lt;/p&gt;
&lt;p&gt;We can also modify our world generation so that it generates stone blocks below Y=0, dirt blocks at Y=1 and air on top of it.&lt;/p&gt;
&lt;p&gt;I&apos;ll change up the loop a bit to make it easier for you to modify it later and add your own stuff!&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-rs&quot;&gt;fn get_or_create_chunk(world: &amp;amp;World, chunk_position: IVec3): &amp;amp;Chunk {
    if !world.chunks.contains(chunk_position) {
        
        var voxels = HashMap::new();

        for z in 0..32 {
            for y in 0..32 {
                for x in 0..32 {
                    var offset = IVec3::new(x, y, z);
                    var world_voxel_position = chunk_position * 32 + offset;

                    if world_voxel_position.y == 0 {
                        voxels.insert(offset, VoxelKind::Dirt);
                    } else if world_voxel_position.y &amp;lt; 0 {
                        voxels.insert(offset, VoxelKind::Stone);
                    }
                }
            }
        }

        world.chunks.insert(chunk_position, Chunk { mesh: none, voxels } )
    }

    return world.chunk.get(chunk_position)
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;And with that, our world generation is complete! It&apos;s not very pretty but I&apos;ll leave the artistic part to you.&lt;/p&gt;
&lt;p&gt;Now, if you played around with it a bit, you might&apos;ve noticed that it&apos;s painfully slow.&lt;br /&gt;
I was hoping to let you run wild for a bit and save the performance talk for later, but looks like we&apos;re dealing with it now.&lt;/p&gt;
&lt;p&gt;Here&apos;s the thing, you might assume that the problem is the HashMap we&apos;re using for each voxel and while that is a part of the problem the bigger problem is that we&apos;re generating way too many unnecessary faces.&lt;/p&gt;
&lt;p&gt;If you look around in your world and maybe try going into the ground you might notice that there&apos;s faces being drawn in-between blocks where it wouldn&apos;t even be visible!&lt;/p&gt;
&lt;p&gt;You might&apos;ve also seen weird artifacting when looking at the ground, those hidden faces are also the reason for that.&lt;/p&gt;
&lt;p&gt;What do we do, you may ask. Well first I&apos;ll need you to make your &lt;code&gt;draw_cube&lt;/code&gt; function into a &lt;code&gt;draw_quad&lt;/code&gt; function because we&apos;ll need to draw one face at a time not all 6. After that, the change is quite simple&lt;/p&gt;
&lt;p&gt;All we need to do is update our &lt;code&gt;create_mesh&lt;/code&gt; function so that for every face we check that there&apos;s no voxel in that direction.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-rs&quot;&gt;fn create_mesh(chunk: &amp;amp;Chunk) {
    var neighbours = [
        (IVec3::new( 1,  0,  0), Direction::Right),
        (IVec3::new(-1,  0,  0), Direction::Left),
        (IVec3::new( 0,  1,  0), Direction::Up),
        (IVec3::new( 0, -1,  0), Direction::Down),
        (IVec3::new( 0,  0,  1), Direction::Forward),
        (IVec3::new( 0,  0, -1), Direction::Back),
    ]
    var vertices = [];

    for (voxel_pos, voxel_kind) in chunk.voxels {
        for (offset, dir) in neighbours {
            if !chunk.voxels.contains(voxel_pos + offset) {
                draw_quad(vertices, voxel_pos, voxel_kind, dir);
            }
        }
    }

    chunk.mesh = Mesh::from_vertices(vertices);
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;And that&apos;s it! By only drawing the faces that are actually visible, we&apos;ve just reduced our vertex count by up to 90%!&lt;/p&gt;
&lt;p&gt;Now, I&apos;ll let you play around with this for a bit. Move around, mess with the world generation (maybe try perlin noise!), have fun with it and when you&apos;re back we&apos;ll add some physics.&lt;/p&gt;
&lt;h2&gt;Physics&lt;/h2&gt;
&lt;p&gt;We&apos;re in the final stretch now. Let&apos;s start by adding a little bit of interactivity by adding raycasting&lt;/p&gt;
&lt;h3&gt;Raycasting&lt;/h3&gt;
&lt;p&gt;Our goal is to make it so we can place &amp;amp; break blocks that we&apos;re looking at.&lt;br /&gt;
To do that, we need to raycast from our camera aka shooting a line from the camera to the world to see which voxel collides first.&lt;/p&gt;
&lt;p&gt;Raycasting is a topic that you can really deep-dive into, heck maybe it&apos;s a deeper topic than voxel engines itself.&lt;br /&gt;
However, the raycasting we need for a voxel engine is really simple. Since our world is a grid we can just step through each voxel one by one in the direction we&apos;re looking at.&lt;/p&gt;
&lt;p&gt;This is usually called a DDA (Digital Differential Analyzer) raycasting. Here&apos;s the idea:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Start from the camera&apos;s position&lt;/li&gt;
&lt;li&gt;Figure out how far you need to go in each axis (x, y, z) before you cross into the next voxel&lt;/li&gt;
&lt;li&gt;Every step, move in the axis that&apos;s closest&lt;/li&gt;
&lt;li&gt;Repeat until you hit something&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;We will also track the last move we&apos;ve made in each step so we can figure out which side of the voxel we&apos;re looking at.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://daymare.net/blogs/voxel-engine-in-a-weekend/assets/dda.png&quot; alt=&quot;DDA Illustration&quot; /&gt;&lt;/p&gt;
&lt;p&gt;If you&apos;re not following any of this, don&apos;t worry me neither. All that matters is that the code we need is:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-rs&quot;&gt;fn raycast_voxel(world: &amp;amp;World, start: Vec3, direction: Vec3, max_dist: float): (IVec3, Vec3)? {
    // the voxel we&apos;re standing in
    var pos = start.floor().as_ivec3();
    // which way we&apos;re stepping (+1 or -1 for each axis)
    var step_dir = direction.sign()

    // how far to step in each axis (smaller means steeper)
    var delta = abs(1 / direction)

    // how far from the current pos to the next voxel boundary
    // for example, if we&apos;re 0.3 into a voxel and going positive X,
    // we have 0.7 to go before hitting the next voxel wall.
    var fract = start - pos.as_dvec3();
    var t_max = Vec3::new(
        if dir.x &amp;gt; 0.0 { 1.0 - fract.x } else { fract.x } * delta.x,
        if dir.y &amp;gt; 0.0 { 1.0 - fract.y } else { fract.y } * delta.y,
        if dir.z &amp;gt; 0.0 { 1.0 - fract.z } else { fract.z } * delta.z,
    )

    var dist = 0.0;
    var last_move = Vec3::ZERO;

    while dist &amp;lt; max_dist {
        if get_voxel(world, pos) != none {
            return (pos, -last_move.normalize());
        }

        // a bunch of fancy maths
        // step in the axis with the smallest t_max — that&apos;s the next voxel boundary
        if t_max.x &amp;lt; t_max.y &amp;amp;&amp;amp; t_max.x &amp;lt; t_max.z {
            pos.x += step.x;
            dist = t_max.x;
            t_max.x += delta.x;
            last_move = Vec3::new(step.x, 0.0, 0.0);
        } else if t_max.y &amp;lt; t_max.z {
            pos.y += step.y;
            dist = t_max.y;
            t_max.y += delta.y;
            last_move = Vec3::new(0.0, step.y, 0.0);
        } else {
            pos.z += step.z;
            dist = t_max.z;
            t_max.z += delta.z;
            last_move = Vec3::new(0.0, 0.0, step.z);
        }

    }
    none
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Now you&apos;ll be missing the &lt;code&gt;get_voxel&lt;/code&gt; function, that&apos;ll look very similar to our &lt;code&gt;set_voxel&lt;/code&gt; function but simpler&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-rs&quot;&gt;fn get_voxel(world: &amp;amp;World, position: IVec3): Voxel? {
    var chunk_position = position.div_euclid(32);
    var local_position = position.rem_euclid(32);

    var chunk = get_or_create_chunk(world, chunk_position);
    return chunk.voxels.get(local_position);
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;That’s it! Now you can raycast from the camera and get which voxel you’re looking at, plus which face you hit. This will come in handy when you want to do something crazy like... break a block or place one next to it.&lt;/p&gt;
&lt;p&gt;So let&apos;s do that. We can delete the code that used the space key to place a voxel and replace it with something like&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-rs&quot;&gt;if input.is_mouse_button_pressed(MouseButton::Left) {
    var result = raycast_voxel(world, camera.position, camera.direction, 3)

    if result != none {
        var (target_block, _) = result
        world.set_voxel(target_block, none)
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;and now we can break blocks! We can do something similar for placing blocks as well, and this is where that last_move value will be useful&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-rs&quot;&gt;if input.is_mouse_button_pressed(MouseButton::Right) {
    var result = raycast_voxel(world, camera.position, camera.direction, 3)

    if result != none {
        var (target_block, last_move) = result
        world.set_voxel(target_block + last_move, Voxel::Stone)
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Just like Minecraft, and every game that tried to be like Minecraft (don&apos;t @ me)&lt;/p&gt;
&lt;p&gt;With this, you can now interact with your world in a much less jank way. And hey, you just did raycasting. Here, take some brownie points because I hated every part of that.&lt;/p&gt;
&lt;h3&gt;AABB Collision&lt;/h3&gt;
&lt;p&gt;And lastly, collision. This one is also a potential rabbit hole, honestly all of physics is, but it&apos;s also quite simple since our world is made up of voxels.&lt;/p&gt;
&lt;p&gt;We&apos;re going to be handling AABB (Axis-Aligned Bounding Box) collision with our world.
So all we care about is whether or not this box is intersecting with the world, if it is, then don&apos;t move there.&lt;/p&gt;
&lt;p&gt;Firstly, we&apos;ll need to give our camera a hitbox. Let&apos;s go with the dimensions (0.8, 1.8, 0.8) for absolutely no reason other than hey Minecraft uses something similar.&lt;/p&gt;
&lt;p&gt;Then, wherever your camera movement code is you&apos;ll want to set it to a variable &lt;code&gt;new_position&lt;/code&gt; before updating the camera&apos;s true position. So that we can check that the new position isn&apos;t inside anything&lt;/p&gt;
&lt;p&gt;After that we can look at our algorithm.&lt;/p&gt;
&lt;p&gt;Basically, first we calculate the delta of our new position and our old position. This gives us a vector telling us how much we should move in each axis.&lt;/p&gt;
&lt;p&gt;Then we can just apply this movement vector for each axis, check that the new position isn&apos;t inside a voxel and then commit it to the camera&apos;s position.&lt;/p&gt;
&lt;p&gt;If it is inside a voxel then we can just cancel out the movement in that direction.&lt;/p&gt;
&lt;p&gt;A simpler way to do collision probably would&apos;ve been to just check after the entire movement but then you wouldn&apos;t be able to walk against a wall so it&apos;s not really nice to play around with.&lt;/p&gt;
&lt;p&gt;And then there&apos;s the actual AABB logic. Basically what we&apos;re going to do is expand our camera&apos;s AABB into our voxel grid.&lt;/p&gt;
&lt;p&gt;After that we can just check that everything inside that box is air.&lt;/p&gt;
&lt;p&gt;We&apos;ll assume our camera position is at the centre of its AABB&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://daymare.net/blogs/voxel-engine-in-a-weekend/assets/aabb.png&quot; alt=&quot;AABB Illustration&quot; /&gt;&lt;/p&gt;
&lt;p&gt;Anyway, here&apos;s the algorithm:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-rs&quot;&gt;// ..
// new_position should be defined above

var aabb_dims = Vec3::new(0.8, 1.8, 0.2);
var aabb_half_dims = aabb_dims / 2;
var delta = new_position - camera.position;

for axis in 0..3 {
    var target_position = new_position;
    target_position[axis] += delta[axis];

    var min = (target_position - aabb_half_dims).floor();
    var max = (target_position + aabb_half_dims).ceil();

    var collided = false;
    for x in min.x..max.x {
        for y in min.y..max.y {
            for z in min.z..max.z {
                var position = IVec3::new(x, y, z);
                if get_voxel(world, position) != none {
                    collided = true;
                    break;
                }
            }

            if collided {
                break;
            }
        }

        if collided {
            break;
        }
    }


    if !collided {
        camera.position[axis] = target_position[axis];
    }

}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Can&apos;t lie, I&apos;m probably not the best person to teach physics but I had to include it. After all, who cares about a world you can&apos;t interact with.&lt;/p&gt;
&lt;p&gt;But now, you can interact with it. And you can move around in it.&lt;/p&gt;
&lt;h2&gt;One last thing..&lt;/h2&gt;
&lt;p&gt;So I left this for the end intentionally since it complicates things but I can&apos;t avoid it anymore, let&apos;s take a look at our Chunk structure&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-rs&quot;&gt;struct Chunk {
    voxels: HashMap&amp;lt;IVec3, VoxelKind&amp;gt;,
    mesh: Mesh?,
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;We&apos;re using a HashMap to store our voxels, however this is a really bad idea for several reasons the most important being that it&apos;s really bad for performance.&lt;/p&gt;
&lt;p&gt;Instead, since our chunks are 32x32x32 (meaning that the IVec3s in the HashMap are always within the range of [0, 32)) we can replace our HashMap with a 3 dimensional array.&lt;/p&gt;
&lt;p&gt;To do that, let&apos;s add an Air type to our VoxelKind first&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-rs&quot;&gt;enum VoxelKind {
    Air,
    Dirt,
    Stone
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;then we can change our Chunk to have a 3D array&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-rs&quot;&gt;struct Chunk {
    voxels: [[[VoxelKind; 32]; 32]; 32],
    mesh: Mesh?,
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Note: I&apos;m indexing as &lt;code&gt;[y][z][x]&lt;/code&gt; here, but feel free to swap it with &lt;code&gt;[x][y][z]&lt;/code&gt; if it makes more sense for your brain.&lt;/p&gt;
&lt;p&gt;Obviously this will cause a lot of errors to pop up in your code. What you&apos;ll need to do is replace all usages of accessing our &lt;code&gt;voxels&lt;/code&gt; data with using our set/get helper functions&lt;/p&gt;
&lt;p&gt;However, those functions do need a little bit of a rework as well&lt;/p&gt;
&lt;p&gt;Our &lt;code&gt;set_voxel&lt;/code&gt; function will no longer take an optional &lt;code&gt;VoxelKind&lt;/code&gt; but instead just take the &lt;code&gt;VoxelKind&lt;/code&gt; itself since we use &lt;code&gt;VoxelKind::Air&lt;/code&gt; for absence of a voxel now.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-rs&quot;&gt;fn set_voxel(world: &amp;amp;World, position: IVec3, kind: VoxelKind) { // !!
    var chunk_position = position.div_euclid(32);
    var local_position = position.rem_euclid(32);

    var chunk = get_or_create_chunk(world, chunk_position);

    chunk.mesh = none;
    
    chunk.voxels[local_position.y][local_position.z][local_position.x] = kind
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;then our &lt;code&gt;get_voxel&lt;/code&gt; function will be&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-rs&quot;&gt;fn get_voxel(world: &amp;amp;World, position: IVec3): VoxelKind { // !!
    var chunk_position = position.div_euclid(32);
    var local_position = position.rem_euclid(32);

    var chunk = get_or_create_chunk(world, chunk_position);
    
    chunk.voxels[local_position.y][local_position.z][local_position.x]
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;After this you&apos;ll be able to make it all work yourself I&apos;m sure. All but the &lt;code&gt;get_or_create_chunk&lt;/code&gt; function and &lt;code&gt;create_mesh&lt;/code&gt; function.&lt;/p&gt;
&lt;p&gt;Let&apos;s take a look at how &lt;code&gt;get_or_create_chunk&lt;/code&gt; changes&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-rs&quot;&gt;fn get_or_create_chunk(world: &amp;amp;World, chunk_position: IVec3): &amp;amp;Chunk {
    if !world.chunks.contains(chunk_position) {
        
        var voxels = [[[VoxelKind::Air; 32]; 32]; 32]; // !!
        if chunk_position.y == 0 {
            for z in 0..32 {
                for x in 0..32 {
                    voxels[0][z][x] = VoxelKind::Dirt;
                }
            }
        }

        world.chunks.insert(chunk_position, Chunk { mesh: none, voxels } )
    }

    return world.chunk.get(chunk_position)
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;That was an obvious fix I know, but then there&apos;s the &lt;code&gt;create_mesh&lt;/code&gt; function, which has one important gotcha that you might miss.&lt;/p&gt;
&lt;p&gt;In our create_mesh function we were only drawing a face if the neighbour didn&apos;t exist (now it&apos;d be if it&apos;s &lt;code&gt;VoxelKind::Air&lt;/code&gt;) but since we were using a HashMap we didn&apos;t need to care if the neighbour was outside of our chunk.&lt;/p&gt;
&lt;p&gt;Now we do. So we&apos;ll need to add an &lt;code&gt;is_oob&lt;/code&gt; flag to the loop, and if the neighbour is out of bounds (meaning that it&apos;s either in the negatives or greater than 32) we&apos;ll treat it as if it was Air. Effectively the same as the old version.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-rs&quot;&gt;fn create_mesh(chunk: &amp;amp;Chunk) {
    var neighbours = [
        (IVec3::new( 1,  0,  0), Direction::Right),
        (IVec3::new(-1,  0,  0), Direction::Left),
        (IVec3::new( 0,  1,  0), Direction::Up),
        (IVec3::new( 0, -1,  0), Direction::Down),
        (IVec3::new( 0,  0,  1), Direction::Forward),
        (IVec3::new( 0,  0, -1), Direction::Back),
    ]
    var vertices = [];

    for (voxel_pos, voxel_kind) in chunk.voxels {
        for (offset, dir) in neighbours {
            var np = voxel_pos + offset;
            var is_oob = np.any(|axis| axis &amp;lt; 0 || axis &amp;gt;= 32);


            if is_oob || chunk.voxels[np.y][np.z][np.x] == VoxelKind::Air {
                draw_quad(vertices, voxel_pos, voxel_kind, dir);
            }
        }
    }

    chunk.mesh = Mesh::from_vertices(vertices);
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;The END!&lt;/h2&gt;
&lt;p&gt;And ta da!!!&lt;/p&gt;
&lt;p&gt;You have a voxel engine! You started with a single cube and now you got chunks, raycasting, physics, and an infinite world. Every voxel engine looks the same at first, so go make it yours.&lt;/p&gt;
&lt;p&gt;And please please please show it off to me on &lt;a href=&quot;https://discord.gg/t7gNX8Kp72&quot;&gt;my discord server&lt;/a&gt; or contact me at &lt;a href=&quot;mailto:contact@daymare.net&quot;&gt;contact@daymare.net&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;If you found this post useful, consider &lt;a href=&quot;https://ko-fi.com/todaymare&quot;&gt;tossing me a coin&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;or is it?&lt;/h2&gt;
&lt;p&gt;But we all know that this isn&apos;t the end but the beginning. Here&apos;s a few challenges if you dare take it upon yourself, I ordered them with my completely arbitrary scale of difficulty&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Gravity &amp;amp; Jumping&lt;/strong&gt;: We have physics, why not make it actually playable!&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Offset the camera&apos;s AABB&lt;/strong&gt;: Right now we assume the camera is in the centre of its AABB, it&apos;s simple but it doesn&apos;t really feel right. Your head is higher than the middle of your body, so try to change that&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Procedural Terrain&lt;/strong&gt;: I didn&apos;t touch upon this in the world generation section since it&apos;s more the artistic part of voxel engines but you should definitely take a look, give your engine some personality. The setup I gave you with &lt;code&gt;get_or_create_chunk&lt;/code&gt; should make it super trivial to add new stuff. Hint, look into using a 2D perlin noise texture as a height map.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Save &amp;amp; Loading&lt;/strong&gt;: I was planning on including this in the article itself but it&apos;s already long enough so I&apos;ll leave it as a challenge for you. Start with saving your chunk data to a binary file and load it in the &lt;code&gt;get_or_generate_chunk&lt;/code&gt; function. After that, you could look into region based loading as well if you&apos;re curious.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Unloading invisible chunks&lt;/strong&gt;: Any chunk outside of the render distance can be unloaded to the disk instead of wasting memory, try periodically checking if the chunk is within the render distance and if not unload it.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Removing the faces between chunks&lt;/strong&gt;: You might&apos;ve noticed from before we added physics that even though we optimized our mesh by a lot there&apos;s still more invisible faces in-between chunks. It&apos;s not too hard to remove those as well, your &lt;code&gt;create_mesh&lt;/code&gt; function will need to know about the surrounding chunks as well. After this is also a pretty good place to stop with the mesh optimizations.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Frustum Culling&lt;/strong&gt;: Right now we&apos;re drawing every chunk within the render distance even if it&apos;s behind us. Try adding frustum culling to only draw the chunks that are actually visible.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Ambient Occlusion&lt;/strong&gt;: There&apos;s this really great article on ambient occlusion for voxels, &lt;a href=&quot;https://0fps.net/2013/07/03/ambient-occlusion-for-minecraft-like-worlds/&quot;&gt;check it out&lt;/a&gt; it really changes the whole vibe of your world from plastic barbie land to an actual world&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Textures!&lt;/strong&gt;: So far I&apos;ve been using plain colours for my voxels, and I can only assume you&apos;ve been doing the same. So try adding textures, maybe even a texture atlas.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Greedy Meshing&lt;/strong&gt;: So, you didn&apos;t heed my warning. That&apos;s fine, &lt;a href=&quot;https://0fps.net/2012/06/30/meshing-in-a-minecraft-game/&quot;&gt;here is your path soldier&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Lighting, of course&lt;/strong&gt;: After ambient occlusion it&apos;s only natural that you try to do lighting. For minecraft style lighting &lt;a href=&quot;https://0fps.net/2018/02/21/voxel-lighting/&quot;&gt;here&apos;s an article you could check out&lt;/a&gt;. Hint, one difference you might notice is that while Minecraft&apos;s chunks are 16x256x16 our chunks are 32x32x32. But in order to implement that lighting system you&apos;ll need to determine a height limit.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Speed, speed, speed&lt;/strong&gt;: &lt;a href=&quot;https://www.youtube.com/watch?v=40JzyaOYJeY&quot;&gt;SPEEEEEED&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Octrees&lt;/strong&gt;: For when HashMaps of chunks are too slow.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Multithreading&lt;/strong&gt;: Here be dragons. Async chunk generation, chunk meshing, cache invalidation, all that and more awaits you. But if you do dare take upon this quest, you better know that the result feels incredible.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Regions&lt;/strong&gt;: Okay this one isn&apos;t actually the hardest but for you to get any use out of it your voxel engine already needs to be stupidly fast. HashMaps are really slow with a lot of entries, so try to group your chunks into 32x32x32 regions. Chunk-ception or something.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Chunk Unloading v2&lt;/strong&gt;: More dragons. You might&apos;ve noticed that your voxel engine is using a lot of memory at high render distances, that&apos;s because each chunk is at least &lt;code&gt;32768&lt;/code&gt; bytes (assuming each voxel is 1 byte). But let me tell you a secret, you don&apos;t need to have the voxel data of the far away chunks loaded. You can just generate the mesh and unload the data for it. Have fun with this information as you will, it&apos;s the difference between 96 render distance using 230+GBs of memory vs 3GBs (&lt;a href=&quot;../godot-ruined-me/&quot;&gt;true story&lt;/a&gt;)&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;???&lt;/strong&gt;: Who says voxels need to be on the CPU?&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
</content></entry><entry><title>So I became God: Artificial Life</title><id>https://daymare.net/blogs/artificial-life</id><updated>2025-11-02T07:50:02.679417958+00:00</updated><author><name>daymare</name><uri>https://daymare.net/</uri></author><category term="BLOGS"/><link href="https://daymare.net/blogs/artificial-life" rel="alternate"/><published>2025-11-02T07:50:02.679417958+00:00</published><summary>&lt;p&gt;Let me take you through a journey of curiosity, growth, and cannibalism. One inspired by a video game and some questionable media..&lt;/p&gt;</summary><content type="html">&lt;p&gt;Let me take you through a journey of curiosity, growth, and cannibalism. One inspired by a video game and some questionable media..&lt;/p&gt;
&lt;p&gt;About a year ago, I watched the &lt;a href=&quot;https://en.wikipedia.org/wiki/Plaything_(Black_Mirror)&quot;&gt;Black Mirror episode &amp;quot;Plaything&amp;quot;&lt;/a&gt; (awesome ep. btw). I didn&apos;t think much of it then but it was my introduction to the idea of artificial life.&lt;/p&gt;
&lt;p&gt;Obviously, even before then I&apos;d seen the fancy YouTube videos, you know the &amp;quot;I made an ecosystem in Unity!&amp;quot; ones. But again, still nothing.&lt;/p&gt;
&lt;p&gt;Until I saw a roguelike called &lt;a href=&quot;https://store.steampowered.com/app/3011360/Primordialis/&quot;&gt;Primordialis&lt;/a&gt;.
It&apos;s this physics-based roguelike where you design your own creature out of cells (muscles, spikes, electric organs, etc.). And your enemies are also designed in the same way. Now I don&apos;t know if you see the vision but if you do, you already know what&apos;s coming.&lt;/p&gt;
&lt;p&gt;While playing the game I just couldn&apos;t help but imagine a world with the same rules of Primordialis, one where the creatures can evolve not only their tiny lil brains but also their body.&lt;/p&gt;
&lt;p&gt;So I became God.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://daymare.net/blogs/artificial-life/./assets/the-first-day.webm&quot; alt=&quot;auto&quot; /&gt;&lt;/p&gt;
&lt;h2&gt;The first day&lt;/h2&gt;
&lt;p&gt;In the beginning, there was nothing.&lt;br /&gt;
And before there was nothing, there was ~~MONSTERS~~ Rust.&lt;/p&gt;
&lt;p&gt;Of course, the first step was to decide what our little goobers (that&apos;s the name I called them in the code, yes) would be made out of. The elements, if you will.&lt;/p&gt;
&lt;p&gt;But before I could even start on the fancy simulation I needed to fill this one glaring hole in my knowledge. I had never touched a neural network in my life!&lt;/p&gt;
&lt;p&gt;So I did what every responsible dev does, I went on YouTube and learned about them. I&apos;m kidding, I just asked ChatGPT to explain it to me. Then I decided to write my own version, because honestly that sounded easier than using Rust libraries¹. I made the simplest of feed-forward neural networks.&lt;/p&gt;
&lt;p&gt;The first goal was simple, collect all the apples as fast as possible.
&lt;img src=&quot;https://daymare.net/blogs/artificial-life/./assets/apples.webm&quot; alt=&quot;auto&quot; /&gt;&lt;/p&gt;
&lt;p&gt;It went in the most boring way ever, it went alright. I had about 100 agents running for 5ish minutes and it had already perfected everything.&lt;/p&gt;
&lt;p&gt;Which, while a little disappointing, meant that I had the green light to go ahead and work on the fancy simulation now.&lt;/p&gt;
&lt;h2&gt;The second day&lt;/h2&gt;
&lt;p&gt;Let there be motion.&lt;/p&gt;
&lt;p&gt;Previously, each goober had its own instance. But now they&apos;d have to share the same resources.&lt;br /&gt;
And for them to actually care about those resources, there had to be stakes.&lt;/p&gt;
&lt;p&gt;The rules of life were simple:&lt;/p&gt;
&lt;p&gt;Eat, you reproduce.&lt;br /&gt;
Move too fast, you get hungry.&lt;br /&gt;
Get too hungry, you die.&lt;/p&gt;
&lt;p&gt;The goobers also got a vector pointing in the direction of the nearest food, ah the simpler times.
Of course, when they reproduced there was a small chance to evolve their brain.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://daymare.net/blogs/artificial-life/./assets/technically-survival-of-the-fittest.webm&quot; alt=&quot;auto&quot; /&gt;&lt;/p&gt;
&lt;p&gt;And then, the great blinding occurred. After that instead of having a direction to the nearest food, they would have to see.&lt;/p&gt;
&lt;p&gt;I made it so each goober had to shoot out 8 rays at slightly different angles in order to see². It&apos;d return a 1 if it was food, -1 if it was another goober and 0 if there was nothing.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://daymare.net/blogs/artificial-life/./assets/the-great-blinding.webm&quot; alt=&quot;auto&quot; /&gt;&lt;/p&gt;
&lt;p&gt;As you can (maybe) see, strategies were starting to develop. Some went with the strategy of vibrating in place, some went with the strategy of hunting food, and some went with the strategy of going as fast as fucking possible, hoping they&apos;d hit some food before they died.&lt;/p&gt;
&lt;p&gt;Well, it&apos;s cool &apos;n all but single cells are BORING.&lt;br /&gt;
So after that little experiment was over I was ready to move onto multicellular organisms, also known as drawing more than one rectangle.&lt;/p&gt;
&lt;p&gt;So I went back to isolated worlds, which started with the goobers being able to modify their velocity directly.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://daymare.net/blogs/artificial-life/./assets/more-apples.webm&quot; alt=&quot;auto&quot; /&gt;&lt;/p&gt;
&lt;p&gt;Which then led to me restricting their movement to &amp;quot;Look Left/Right&amp;quot; and &amp;quot;Move forward&amp;quot;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://daymare.net/blogs/artificial-life/./assets/are-they-stupid.webm&quot; alt=&quot;auto&quot; /&gt;&lt;/p&gt;
&lt;p&gt;Are they stupid?&lt;br /&gt;
Yeah.&lt;br /&gt;
They&apos;re trying, okay&lt;/p&gt;
&lt;h2&gt;The third day&lt;/h2&gt;
&lt;p&gt;Let there be... fat people?&lt;/p&gt;
&lt;p&gt;Now that we have goobers, and they can move around, eat food, and reproduce we can finally get to the entire point of this project and give them more cells to work with.&lt;/p&gt;
&lt;p&gt;At first I had very simple cell types that were basically stat modifiers. There was a SpeedyCell that increased your max speed &amp;amp; decreased how much energy you lost from speeding. Or a basic cell that just increased your mass.&lt;br /&gt;
Oh and also a HealthyCell which decreased the basal metabolic rate and fat cells that could increase the maximum energy they could store.&lt;/p&gt;
&lt;p&gt;And uh.. that was sorta it for a bit but I&apos;ll get to that later.³&lt;/p&gt;
&lt;p&gt;Of course cells came with some drawbacks as well. Each cell would increase your mass and weight, the heavier you got the slower you could move and the more energy it took to do the same actions.&lt;br /&gt;
Which obviously, led to the sword meta.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://daymare.net/blogs/artificial-life/./assets/the-sword-meta.webm&quot; alt=&quot;auto&quot; /&gt;&lt;/p&gt;
&lt;p&gt;So uh, this is about the point where I started to fear I created a cult. But obviously the swords had to be stopped.&lt;/p&gt;
&lt;p&gt;My mate Kiniro (the dev of the game &lt;a href=&quot;https://store.steampowered.com/app/3943850/Faster_Bunnies/&quot;&gt;Faster Bunnies&lt;/a&gt;) suggested a great fix: make goobers grow to their full size before they can spawn children.&lt;/p&gt;
&lt;p&gt;Which actually worked!&lt;/p&gt;
&lt;p&gt;Except now they were just not growing at all.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://daymare.net/blogs/artificial-life/./assets/smol.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;So I thought: well, if these guys don&apos;t want to grow, I&apos;ll just let them fight it out.&lt;/p&gt;
&lt;h2&gt;And on the fourth day..&lt;/h2&gt;
&lt;p&gt;I let them fight.&lt;/p&gt;
&lt;p&gt;I gave them weapons (Spike cells). I gave them shields (..shield cells). But most importantly, I gave them an incredible 12 floats of memory⁴ so they could avenge their father.
&lt;img src=&quot;https://daymare.net/blogs/artificial-life/./assets/i-let-them-fight.webm&quot; alt=&quot;auto&quot; /&gt;&lt;/p&gt;
&lt;p&gt;Combat wasn&apos;t random either, getting hit on the side dealt more damage than getting hit on the head. And every collision damaged both sides. I thought they&apos;d learn to be strategic about it, angling attacks &amp;amp; dodging, but it mostly just led to the big guys eating their babies for food.&lt;/p&gt;
&lt;p&gt;Evolution is a cruel thing, huh?&lt;/p&gt;
&lt;h2&gt;On the fifth day&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;the scientists who studied the rivers&lt;br /&gt;
were forbidden to speak&lt;br /&gt;
or to study the rivers.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Which is totally irrelevant. I just didn&apos;t have a better way of ending this post.&lt;/p&gt;
&lt;p&gt;This was a really fun experiment that I&apos;m definitely planning on returning to⁵.&lt;/p&gt;
&lt;p&gt;If you enjoyed it, laughed, or want to join the cult of rectangles. Maybe &lt;a href=&quot;https://ko-fi.com/todaymare&quot;&gt;buy me a coffee&lt;/a&gt;?&lt;br /&gt;
Or if you would like to fork around &lt;a href=&quot;https://github.com/todaymare/evolution-sim&quot;&gt;here&apos;s the repo&lt;/a&gt;. If you have any questions or feedback feel free to &lt;a href=&quot;https://discord.gg/t7gNX8Kp72&quot;&gt;join my discord server&lt;/a&gt; or mail me at &lt;a href=&quot;mailto:contact@daymare.net&quot;&gt;contact@daymare.net&lt;/a&gt;&lt;/p&gt;
&lt;hr /&gt;
&lt;p&gt;¹: Related: &lt;a href=&quot;../everbody-so-creative/&quot;&gt;Everybody&apos;s So Creative!&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;²: To be honest the way I handled raytracing was horribly inefficient. I&apos;ve made raytracers before so I know that they can be fast, it&apos;s just that I really could not care less about it. I wonder how many hours of simulation time I could&apos;ve saved had I only made it slightly more optimized by just adding a spatial grid or something&lt;/p&gt;
&lt;p&gt;³: After reproduction each goober had a small chance to mutate their neural-network and/or delete/add/modify one of their cells.&lt;/p&gt;
&lt;p&gt;⁴: I felt like a genius coming up with this idea, though I have no idea if the neural networks ever used it. It&apos;s basically just 12 floats that get passed into the neural-network and then are preserved for the next iteration. Yes, that&apos;s what I called &amp;quot;memory&amp;quot;. You know, so they could store information about life&apos;s most important questions such as &amp;quot;Is this food?&amp;quot;, &amp;quot;Is this enemy?&amp;quot;, &amp;quot;Am I food?&amp;quot;, etc.&lt;/p&gt;
&lt;p&gt;⁵: Okay this one probably could&apos;ve been inlined but I wanted to talk a bit more about it after I had already finished writing. I think the biggest failure of this experiment was that the goobers never really got a chance to live, their really short life revolved around &amp;quot;eat food as fast as possible&amp;quot; with nothing else they could do. Maybe in another iteration I could allow them to build, move food around, live 5 mins on a single piece of food instead of 30 seconds and just be more patient with them. There&apos;s a lot of ground to explore here.⁶&lt;/p&gt;
&lt;p&gt;⁶: I really enjoyed writing these footnotes and this entire post in general, can you tell?⁷&lt;/p&gt;
&lt;p&gt;⁷: Fun fact, the videos and screenshots for this post were taken months before I even had the idea for the website. So it&apos;s a total coincidence that the colours fit so perfectly.. wait is my favourite colour green??&lt;/p&gt;
</content></entry><entry><title>This week in margarine</title><id>https://daymare.net/blogs/this-week-in-margarine</id><updated>2025-10-26T06:46:11.276630437+00:00</updated><author><name>daymare</name><uri>https://daymare.net/</uri></author><category term="BLOGS"/><link href="https://daymare.net/blogs/this-week-in-margarine" rel="alternate"/><published>2025-10-26T06:46:11.276630437+00:00</published><summary>&lt;p&gt;A quick update on the progress of my programming language margarine. It&apos;s build system, why it won&apos;t have a JIT, and other design decisions.&lt;/p&gt;</summary><content type="html">&lt;p&gt;A quick update on the progress of my programming language margarine. It&apos;s build system, why it won&apos;t have a JIT, and other design decisions.&lt;/p&gt;
&lt;p&gt;For those who don&apos;t know margarine, it&apos;s my programming language that&apos;s meant to be a replacement for Lua in my own projects, &lt;a href=&quot;../four-years-five-failures-one-compiler/&quot;&gt;check out this post&lt;/a&gt; if you want to learn more about it.&lt;/p&gt;
&lt;p&gt;Hello! So here&apos;s the thing, this week I decided that I wanted to be done with margarine as soon as possible.
But I&apos;m also quite tired of having way too many unfinished projects, so I had to decide on a to-do list that I needed to finish before I could call margarine done. And before you click off, a JIT was in this to-do list until yesterday.&lt;/p&gt;
&lt;p&gt;Here, I&apos;ll show you the to-do list.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;a basic JIT&lt;/li&gt;
&lt;li&gt;finishing up the build system&lt;/li&gt;
&lt;li&gt;a GC&lt;/li&gt;
&lt;li&gt;some bug fixes&lt;/li&gt;
&lt;li&gt;stdlib&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;So me being me I went with the one I have never done before, the JIT.&lt;/p&gt;
&lt;h2&gt;The JIT&lt;/h2&gt;
&lt;p&gt;In a previous version of margarine I had used LLVM¹ to compile to native code and as far as I remember it was a horrible experience. Heck when I went back to margarine it didn&apos;t even compile even though literally nothing about my build system had changed.&lt;/p&gt;
&lt;p&gt;So this time I decided to use something easier, something shinier, something newer, something called &lt;a href=&quot;https://cranelift.dev/&quot;&gt;Cranelift&lt;/a&gt;. Which claims to be faster than LLVM while admitting that it has less optimisations. That sounds like a good trade-off, right? It probably doesn&apos;t have many of the niche optimisations that LLVM has built up over the years but it has the important optimisations, right?&lt;/p&gt;
&lt;p&gt;That sounded amazing! Not only did it mean that I didn&apos;t need to deal with LLVM&apos;s jankness but also that the JIT will compile code faster!&lt;/p&gt;
&lt;p&gt;Now, I don&apos;t need margarine&apos;s JIT to be perfect or even optimized. I just want to add a JIT to the runtime to make it faster and I want to get it over with quickly so I could move onto the next thing in the to-do list. So I just made the JIT convert my interpreter one-to-one.&lt;/p&gt;
&lt;p&gt;I think this&apos;ll make more sense if I showed some code, so let&apos;s take the &lt;code&gt;AddInt&lt;/code&gt; instruction. (fyi, margarine&apos;s interpreter is a stack based interpreter)&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-rs&quot;&gt;AddInt =&amp;gt; {
   let rhs = stack.pop().as_int();
   let lhs = stack.pop().as_int();
   stack.push(Value::Int(lhs + rhs))
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;and so the JIT would just do that exact same thing but inlined. Which made the JIT the same as the interpreter except without the instruction over-head and any optimisations Cranelift could apply along-side it.&lt;/p&gt;
&lt;p&gt;Now this meant that the JIT would often generate redundant loads and stores, stuff like store x then immediately override x. Which was fine, I thought. It sounds like a basic optimisation, surely Cranelift wouldn&apos;t even break a sweat, right?&lt;/p&gt;
&lt;p&gt;Other than the fact that this isn&apos;t much of a basic optimisation, Cranelift doesn&apos;t even try to do anything about it. So I was left on my own.&lt;/p&gt;
&lt;p&gt;Is this an unsolvable problem? No.
Is there nothing I can do about it? Certainly not.
Is this a problem I wanted to have when I decided to JIT? No, but it is what it is.&lt;/p&gt;
&lt;p&gt;So I got onto brainstorming how I could go about solving this.
For one, I could switch to a register based VM which would immediately eliminate any interaction with the stack since I can just use cranelift variables. But this would probably require me to rewrite my entire VM and some form of register-allocation would be practically necessary to keep the register count under 256 (one byte).&lt;/p&gt;
&lt;p&gt;Or I could convert the stack into cranelift variables at runtime, though that would require me to know the stack height at any given instruction. The idea would be to convert each stack slot to a Cranelift variable and simulate push &amp;amp; pops.&lt;/p&gt;
&lt;p&gt;There&apos;s many ways I could make this JIT thing work, but after talking to people I&apos;m not sure if it&apos;s even worth it. Sure, it&apos;d make things faster but there&apos;s a lot more I should be focusing on right now (many things that when implemented would probably require the JIT to be reworked anyways).&lt;/p&gt;
&lt;p&gt;So I moved onto the next item on the list.&lt;/p&gt;
&lt;p&gt;¹: Writing this some part of me wishes I never ditched compiling to native code. I know it was the right decision but the &lt;em&gt;speed&lt;/em&gt; is very alluring especially now that I don&apos;t even have a JIT. Woman in the red dress or something I dunno.&lt;/p&gt;
&lt;h2&gt;The build system&lt;/h2&gt;
&lt;p&gt;This part of margarine has gone through a few iterations already. At the start, I just had to specify every single file I wanted to include in the CLI tool, which led to commands like&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;margarine std std/duration std/list std/rand raylib raylib/keys raylib/window flappy_bird
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This ability to be extremely granular on what files to import was a design decision that I wanted to preserve since margarine is meant to be an embeddable language. But of course, we also need margarine to stand on its own, so I had to figure out something else.&lt;/p&gt;
&lt;p&gt;The first thing I tried was to just copy Rust. I had a &lt;code&gt;build.toml&lt;/code&gt; file where you could specify your dependencies, and your code would be in a &lt;code&gt;src/&lt;/code&gt; directory with the entry point being a function named &lt;code&gt;main&lt;/code&gt; in &lt;code&gt;src/main.mar&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;This worked for a bit; the CLI tool would fetch the github repos of the dependencies, cache them in an &lt;code&gt;artifacts/&lt;/code&gt; folder and compile and then run the program. But I wanted more, because if we&apos;re creating this entire build system, we might as well make it more powerful.&lt;/p&gt;
&lt;p&gt;That brings us to &lt;code&gt;build.mar&lt;/code&gt;. When compiling, the &lt;code&gt;std&lt;/code&gt; library is always included, and then the &lt;code&gt;build&lt;/code&gt; function is called. The &lt;code&gt;std&lt;/code&gt; library exposes the &lt;code&gt;CompilationUnit&lt;/code&gt; type, an object that represents the current program&apos;s files and dependencies.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-rs&quot;&gt;fn build() {
 var unit = CompilationUnit::new();
 unit.fetch(&amp;quot;somelib&amp;quot;, &amp;quot;https://some.other/library&amp;quot;);
 unit.import(&amp;quot;std/&amp;quot;);
 unit.build();
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This version isn&apos;t final because it doesn&apos;t handle libraries having dependencies. My current idea is to change the build function&apos;s signature to &lt;code&gt;fn build(): CompilationUnit&lt;/code&gt;, letting libraries return their dependency info, allowing the creation of a single unit recursively.&lt;/p&gt;
&lt;p&gt;Side note, here&apos;s a funny thing I discovered while working on this. Since margarine can run with compiler errors, as long as you don&apos;t actually execute the errors, you can actually compile the current file with new libraries.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-rs&quot;&gt;fn build() {
 var unit = CompilationUnit::new();
 unit.fetch(&amp;quot;rand&amp;quot;, &amp;quot;https://github.com/todaymare/margarine-rand&amp;quot;);
 unit.import(&amp;quot;&amp;quot;);
 unit.build()
}

fn main() {
   print(rand::rand())
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;which will work perfectly fine. Apart from spamming your terminal with compiler errors.&lt;/p&gt;
&lt;p&gt;Back to the build system, another thing I recently considered was to go with Go style imports. You know, just&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;extern &amp;quot;https://github.com/todaymare/margarine-std&amp;quot; as std
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;in the middle of the program. This would allow me to not have a build system, but I&apos;m not sure. I&apos;ll have to think more about this whole build system thing.&lt;/p&gt;
&lt;p&gt;It&apos;s kind of annoying to constantly be working on things that aren&apos;t guaranteed to be finished, so I just decided to quickly write up something that&apos;s very important and is pretty much as complete as I&apos;ll ever make it be&lt;/p&gt;
&lt;h2&gt;The GC&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;is garbage collection just a metaphor for letting go?&lt;br /&gt;
- William Shakespeare&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;We&apos;re coming to the end of the week and while I have experimented with a lot of things I wouldn&apos;t call those two complete just yet. However, the garbage collection for margarine is complete. It&apos;s definitely not the fastest or the most optimal but it does clean up garbage when needed so it&apos;s fine.&lt;/p&gt;
&lt;p&gt;What I did you might ask? I just copy-pasted the garbage collection I had for my old project &lt;a href=&quot;https://github.com/todaymare/azurite&quot;&gt;azurite&lt;/a&gt;. I know I know lame! It&apos;s just a basic mark &amp;amp; sweep world-stopping tracing garbage collector.&lt;/p&gt;
&lt;p&gt;There isn&apos;t much to say about it. It just goes through the stack, marks objects as live and then kills any that are dead. Oh and I guess it&apos;s only called when you fail to allocate an object.&lt;/p&gt;
&lt;h2&gt;End of the Week&lt;/h2&gt;
&lt;p&gt;Anyway, that&apos;s where margarine stands this week. A tiny bit more of a real boy.&lt;/p&gt;
&lt;p&gt;Thank you for reading so far and if you enjoyed this post or have feedback on any of the design decisions I talked above consider joining &lt;a href=&quot;https://discord.gg/t7gNX8Kp72&quot;&gt;my discord server&lt;/a&gt;, or you can e-mail me at &lt;a href=&quot;mailto:contact@daymare.net&quot;&gt;contact@daymare.net&lt;/a&gt;. Or if you really enjoyed it, consider &lt;a href=&quot;https://ko-fi.com/todaymare&quot;&gt;buying me a cup of coffee&lt;/a&gt;&lt;/p&gt;
</content></entry><entry><title>Everybody&apos;s so Creative!</title><id>https://daymare.net/blogs/everbody-so-creative</id><updated>2025-10-19T08:39:24.740553972+00:00</updated><author><name>daymare</name><uri>https://daymare.net/</uri></author><category term="BLOGS"/><link href="https://daymare.net/blogs/everbody-so-creative" rel="alternate"/><published>2025-10-19T08:39:24.740553972+00:00</published><summary>&lt;p&gt;After 4 years with Rust, I love the language – but I’m starting to think the ecosystem has an abstraction addiction. Or: why every Rust crate feels like a research paper on abstraction.&lt;/p&gt;</summary><content type="html">&lt;p&gt;After 4 years with Rust, I love the language – but I’m starting to think the ecosystem has an abstraction addiction. Or: why every Rust crate feels like a research paper on abstraction.&lt;/p&gt;
&lt;p&gt;Hi, it’s me again. Back at it with another rant thinly disguised as a &amp;quot;think piece.&amp;quot;&lt;/p&gt;
&lt;h2&gt;The Question Nobody Wants to Answer&lt;/h2&gt;
&lt;p&gt;Have you ever wanted to build something simple in Rust?
Maybe you reach for &lt;code&gt;bevy&lt;/code&gt;, or &lt;code&gt;wgpu&lt;/code&gt;, or some other library that promises everything you didn&apos;t ask for.&lt;/p&gt;
&lt;p&gt;And then you hit that moment – you&apos;re debugging, you hit &amp;quot;Go to Definition&amp;quot;, and suddenly you&apos;re free falling through ten layers of traits, macros, and generics just to figure out how a buffer updates.&lt;/p&gt;
&lt;p&gt;If that sounds familiar, congratulations: you&apos;ve met Rust&apos;s favourite pastime – abstraction as performance art.&lt;/p&gt;
&lt;h2&gt;Why Are We Like This?&lt;/h2&gt;
&lt;p&gt;I get it – abstractions are cool. They’re supposed to hide complexity so we can focus on cooler stuff. And Rust loves that idea. Traits, generics, lifetimes – layer upon layer of &amp;quot;don’t worry about it honey.&amp;quot;&lt;/p&gt;
&lt;p&gt;Take &lt;code&gt;nalgebra&lt;/code&gt;. Fantastic crate – powerful, flexible, and deeply overqualified for 90% of use cases.&lt;/p&gt;
&lt;p&gt;It’s not that I hate nalgebra – it’s brilliant. I just hate that this kind of maximalism has become the default.&lt;/p&gt;
&lt;p&gt;Want to do something slightly off-script?&lt;br /&gt;
That’ll be three trait bounds, one custom derive, and a spiritual journey through &lt;code&gt;src/internal/utils/mod.rs&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;You might say these libraries are built this way because we don’t know what the user might want – and fair enough, that’s been the curse of library design since the dawn of libraries. But not every problem needs a skyscraper of abstractions; most of the time, all you need is a shed.&lt;/p&gt;
&lt;p&gt;Take &lt;code&gt;glam&lt;/code&gt;, for example, which doesn’t try to solve philosophy – it just does math. You don’t need a PhD in generics to understand what &lt;code&gt;Vec3::normalize()&lt;/code&gt; does, and that’s exactly the point.&lt;/p&gt;
&lt;p&gt;But the thing is – &lt;code&gt;nalgebra&lt;/code&gt; isn&apos;t an isolated example. It’s cultural.&lt;/p&gt;
&lt;p&gt;And that’s the real cost of abstraction – it makes the ceiling higher, but it also makes the floor invisible. (Shakespeare got nothin’ on me)&lt;/p&gt;
&lt;p&gt;The real tragedy? Once you see it, you start writing that way too.
You start thinking, &amp;quot;Well, maybe I should make this generic in case someone wants to use quaternions instead of matrices...&amp;quot; and suddenly, congratulations – you&apos;re building for someone who doesn&apos;t exist.&lt;/p&gt;
&lt;h2&gt;Rust, the Language&lt;/h2&gt;
&lt;p&gt;Rust is a great language. I’ll be the first to say that. It’s not perfect – no language is except &lt;a href=&quot;https://github.com/todaymare/margarine&quot;&gt;margarine&lt;/a&gt; – but it’s the tool I reach for first.&lt;/p&gt;
&lt;p&gt;That said, the Rust ecosystem feels like it&apos;s divided into two sides. (Ironic, isn’t it?)&lt;/p&gt;
&lt;p&gt;On one side, you’ve got artists. The folks who treat Rust like an art form – every crate is a masterpiece of generics, lifetimes, and zero-cost abstractions. They push the language to its limits, and honestly? It’s incredible to watch.&lt;/p&gt;
&lt;p&gt;On the other side are the people trying to ship things, the ones who’d use Zig if it weren’t allergic to syntactic sugar. They don’t care about elegance or clever abstractions – they just want their code to compile before they age into retirement.&lt;/p&gt;
&lt;p&gt;That phrasing sounds harsh, but the thing is: neither side is wrong.&lt;/p&gt;
&lt;p&gt;Let&apos;s get one thing straight: over-engineering in Rust is incredibly fun. The language hands you these shiny tools and it&apos;s hard not to play with them. It&apos;s like LEGO for programmers.&lt;/p&gt;
&lt;p&gt;And hey, when you can push the boundaries of abstractions and keep things fast? It feels like you&apos;ve cracked the code to the universe.&lt;/p&gt;
&lt;p&gt;But still; most people just want to see what the code does, not spelunk through an art installation of traits.&lt;/p&gt;
&lt;p&gt;The core problem is that Rust being Rust turns code you can’t understand into the default.
The community shows up to tell you that if you don’t over-complicate it, you’re writing unidiomatic Rust.&lt;/p&gt;
&lt;p&gt;If &amp;quot;Go to Definition&amp;quot; can’t take me to your implementation and I have to dig through your GitHub repo just to see how Matrix4::mul works – can I really say I know the code I’m using?&lt;/p&gt;
&lt;p&gt;For many people, maybe that’s fine. But every dependency you bring in is still your responsibility. Obviously I don’t understand every library I use – that’d be absurd – but I’d like to live in a world where I can understand the code I bring in.&lt;/p&gt;
&lt;p&gt;And just to be clear: abstraction isn’t the enemy. It’s what lets you write 3D engines or HTTP servers without caring about hardware or the TCP stack.
The problem is when we build for someday instead of today.&lt;/p&gt;
&lt;p&gt;I’m not saying stop writing clever code – just make sure it earns its keep. When you feel that &amp;quot;maybe I should make this generic&amp;quot; impulse, ask: Who benefits from this, today?
If the answer is &amp;quot;future me,&amp;quot; maybe wait until future you actually shows up.&lt;/p&gt;
&lt;p&gt;Here’s my rule of thumb: &lt;strong&gt;keep &amp;quot;Go to Definition&amp;quot; useful.&lt;/strong&gt;&lt;/p&gt;
&lt;h1&gt;The Conclusion&lt;/h1&gt;
&lt;p&gt;I&apos;ve been on both sides of the extreme. Heck, my first time trying to learn OpenGL I tried to write a compile-time zero-cost abstraction for it while I was learning it. Later, I just went with raw OpenGL in a dozen different projects.
So, yeah, I&apos;ve lived both sides of the story.&lt;/p&gt;
&lt;p&gt;From that experience, I&apos;ve learned one thing: things are almost always easier when there are fewer moving parts. Simpler code doesn&apos;t mean worse code – it just means you can still understand it six months later.&lt;/p&gt;
&lt;p&gt;So maybe try the other side of the spectrum if you haven&apos;t. Or don&apos;t.&lt;/p&gt;
&lt;p&gt;Anyway, this post will probably age terribly. Someone will send it to me in two years while I’m working on my eighth trait-based ECS library written entirely in macros.&lt;/p&gt;
&lt;p&gt;But for now, I just wanted to encourage maybe one person to try to write code that&apos;s just code... not abstracted, not filled with traits or generics... just code.&lt;/p&gt;
&lt;p&gt;Maybe one day the novelty will wear off, and Rust will chill out.&lt;/p&gt;
&lt;p&gt;Until then, I&apos;ll be over here using &lt;code&gt;glam&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;If you enjoyed this post, I&apos;d love it if you could whisper &apos;nice post&apos; at your screen... &lt;a href=&quot;https://ko-fi.com/todaymare&quot;&gt;or drop me a coffee&lt;/a&gt;.&lt;br /&gt;
Or &lt;a href=&quot;https://discord.gg/t7gNX8Kp72&quot;&gt;join the discord&lt;/a&gt;, tell me what you think.&lt;/p&gt;
&lt;p&gt;P.S grammarly forked me over vro 🥀&lt;/p&gt;
</content></entry><entry><title>Godot Ruined My Sense of Speed</title><id>https://daymare.net/blogs/godot-ruined-me</id><updated>2025-10-12T21:09:01.870614127+00:00</updated><author><name>daymare</name><uri>https://daymare.net/</uri></author><category term="BLOGS"/><link href="https://daymare.net/blogs/godot-ruined-me" rel="alternate"/><published>2025-10-12T21:09:01.870614127+00:00</published><summary>&lt;p&gt;Godot didn’t just ruin my game. It ruined how I think about performance and abstraction. What started as a simple voxel prototype spiraled into an existential crisis&lt;/p&gt;</summary><content type="html">&lt;p&gt;Godot didn’t just ruin my game. It ruined how I think about performance and abstraction. What started as a simple voxel prototype spiraled into an existential crisis&lt;/p&gt;
&lt;p&gt;It was a sunny day (probably), a few months back. I was in a hotel room on my laptop – the one I definitely bought for studying – playing Factorio, like any good student does.
And then I had a brilliant idea.
An automation game.
Factorio, but bigger! And better!&lt;/p&gt;
&lt;p&gt;Did it matter that I had no experience making games?
Don&apos;t be ridiculous, of course not!&lt;/p&gt;
&lt;p&gt;The plan: mix Factorio’s automation, Terraria’s boss fights, and a Minecraft-esque voxel world.
So I said, considering I have no graphics development experience I should start simple – and make a voxel engine&lt;/p&gt;
&lt;h2&gt;The Prototype&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;All good games need a prototype&lt;br /&gt;
- Sun Tzu&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Naturally, the next step was to pick an engine.&lt;br /&gt;
I wanted something fast, simple, cross-platform – and preferably not Unity, because, well, Unity.&lt;/p&gt;
&lt;p&gt;Godot seemed perfect. Lightweight, open source, recently got a shiny new 3D renderer (and also because UE hates MacOS)&lt;/p&gt;
&lt;p&gt;Of course, I knew GDScript wasn&apos;t going to survive a voxel world. So I wrote the simulation in Rust and used Godot purely for visuals.&lt;br /&gt;
Simple plan, what could possibly go wrong?&lt;/p&gt;
&lt;p&gt;So I started.&lt;br /&gt;
Made a Rust extension. Drew a quad. Then a cube. Then a whole chunk.&lt;br /&gt;
Suddenly I had mining. Machines. Inserters. Belts. Quarries. Crafting.&lt;/p&gt;
&lt;p&gt;I was unstoppable running purely on caffeine and motivation.&lt;br /&gt;
It might sound like I&apos;m glossing over the implementation, but the entire prototype took literally two days.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://daymare.net/blogs/godot-ruined-me/assets/prototype.png&quot; alt=&quot;An image of the prototype&quot; /&gt;&lt;/p&gt;
&lt;p&gt;I had so many conversations about how the quarry should work with my friends. It was an interesting design problem: finding upgrades for quarries that weren&apos;t just &amp;quot;the same thing, but faster&amp;quot;, especially since the quarries were meant to dig into the world and destroy it.&lt;/p&gt;
&lt;p&gt;That also turned quarries into a single-pipe problem — where common resources clog the system and starve rarer ones. Totally irrelevant for this prototype though, since items were just physics bodies moving on top of belts.&lt;/p&gt;
&lt;p&gt;Anyways, I was ready. I had spent roughly 32 hours over 2 days working on the first prototype of my life. I went outside to grab some food, came back, and even arranged a play-test for the next day.&lt;/p&gt;
&lt;p&gt;Everything was perfect.&lt;br /&gt;
The engine worked. The game ran smoothly – until it didn&apos;t.&lt;/p&gt;
&lt;h3&gt;Things go VERY WRONG&lt;/h3&gt;
&lt;p&gt;Look, I&apos;m lucky enough to have a great computer. During my testing I couldn&apos;t even get the game to go below 144FPS.&lt;br /&gt;
But see, I never tried throwing more quarries at it because I assumed any slow-down would come from the naturally expensive thing – the rigidbodies that I used for items.&lt;/p&gt;
&lt;p&gt;In order for this to make sense I need to show you how procedurally generating a chunk goes in Godot.&lt;/p&gt;
&lt;p&gt;A chunk is just a mesh made of triangles made of vertices.
In the simplest voxel engines, you’d generate a quad for every block face — slow, but simple.
For the prototype, I added a very naive culling algorithm so it wouldn’t generate hidden faces.&lt;/p&gt;
&lt;p&gt;So far so good. This entire algorithm was implemented in Rust (&lt;em&gt;blazing fast!&lt;/em&gt;).&lt;/p&gt;
&lt;p&gt;Godot exposes a type called &lt;a href=&quot;https://docs.godotengine.org/en/stable/classes/class_surfacetool.html&quot;&gt;SurfaceTool&lt;/a&gt;, which isn&apos;t technically the lowest-level API they provide but it is pretty much a drop-in replacement for pushing individual vertices into a list.&lt;/p&gt;
&lt;p&gt;Anyway, here’s how that looked in code – calling into Godot from Rust, in order to draw a quad:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-rust&quot;&gt;fn draw_quad(st: &amp;amp;mut SurfaceTool, k: &amp;amp;mut i32, quad: Quad) {
    let normal = match quad.direction {
        ...
    };

    st.set_color(quad.color);
    st.set_normal(normal);
    for corner in quad.corners {
        st.add_vertex(corner);
    }


    st.add_index(*k);
    st.add_index(*k + 1);
    st.add_index(*k + 2);
    st.add_index(*k + 2);
    st.add_index(*k + 3);
    st.add_index(*k);

    *k += 4;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;And here’s what “just pushing to a list” looks like in comparison:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-rust&quot;&gt;fn draw_quad(vertices: &amp;amp;mut Vec&amp;lt;Vector3&amp;gt;, indices: &amp;amp;mut Vec&amp;lt;i32&amp;gt;, k: &amp;amp;mut i32, quad: Quad) {
    let normal = match quad.direction {
        ...
    };

    for corner in quad.corners {
        vertices.push(corner);
    }


    indices.push(*k);
    indices.push(*k + 1);
    indices.push(*k + 2);
    indices.push(*k + 2);
    indices.push(*k + 3);
    indices.push(*k);

    *k += 4;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;You might notice there’s not much difference, so really, how bad could &lt;a href=&quot;https://docs.godotengine.org/en/stable/classes/class_surfacetool.html&quot;&gt;SurfaceTool&lt;/a&gt; be?&lt;/p&gt;
&lt;p&gt;8 milliseconds per chunk! Not terrible – until you realize doing it manually takes 300 microseconds. Twenty-seven times faster.&lt;/p&gt;
&lt;p&gt;And that’s not even counting Godot’s mesh conversion time, which I’m pretty sure happens way later.&lt;/p&gt;
&lt;p&gt;At this point you can probably guess what went wrong.
When my friend play-tested the game, the framerate dropped to a cinematic 10 FPS within an hour.&lt;/p&gt;
&lt;p&gt;Why didn&apos;t I notice it during my own testing?
Simple, I was doing things efficiently. I already knew what to build, nothing went to waste.&lt;br /&gt;
He, on the other hand, decided to cover the planet in quarries (like, five quarries).&lt;/p&gt;
&lt;p&gt;After it dropped to 10 FPS, he stopped playing. Which, fair.&lt;br /&gt;
I still count an hour-long playtest as a win – but the reason he stopped playing was... traumatizing.&lt;/p&gt;
&lt;h1&gt;The Rebound&lt;/h1&gt;
&lt;p&gt;The prototype was a success, somewhat.&lt;br /&gt;
So now it was time to make the actual game, properly this time.&lt;/p&gt;
&lt;p&gt;After that Godot fiasco I decided I&apos;d just go pure Rust.&lt;/p&gt;
&lt;p&gt;But you can&apos;t just start a project, you need an engine!
And it needs to be super performant because I never want to see someone stop playing because of performance again.&lt;/p&gt;
&lt;p&gt;Okay, calm down champ.&lt;/p&gt;
&lt;p&gt;After the Godot incident (yes, I&apos;m going to call it that, fight me) I over-corrected hard in the other direction to the point where I was seriously considering making a full-blown game engine.&lt;br /&gt;
Which, granted, isn&apos;t entirely out of the question for me... but still, absurdly unnecessary.&lt;/p&gt;
&lt;p&gt;Eventually, a brick hit my head and I realized I could just... make a game.
No engine. No framework. Just raw OpenGL and a giant while loop.&lt;/p&gt;
&lt;p&gt;Of course you don&apos;t need fancy frameworks or giant game engines... You can just make a game.&lt;/p&gt;
&lt;p&gt;At the time, however, this was a big revelation for me.&lt;/p&gt;
&lt;p&gt;So I opened up LearnOpenGL and started replicating my Godot voxel engine one-to-one. Which was quite easy, considering I had to do everything manually anyway.&lt;br /&gt;
I had a pretty good voxel engine by this point, it was so much faster than Godot could&apos;ve ever been, but now I had to actually implement game features.&lt;/p&gt;
&lt;p&gt;The first of many being machines.&lt;/p&gt;
&lt;p&gt;You see in games like Factorio many machines don&apos;t need to always run. In fact, they don&apos;t! Most machines take in some sort of input, process it for N frames, and then do something as a result of it.&lt;br /&gt;
This sort of system lands itself very well to a WorkQueue.&lt;/p&gt;
&lt;p&gt;Basically, whenever a machine needed to do something again in 20 ticks it would tell the WorkQueue &amp;quot;hmu in 20 ticks&amp;quot; and then the WorkQueue would handle that accordingly.&lt;br /&gt;
This system made sure that even if you had millions of machines only the ones that needed to be updated this tick would be accessed.&lt;/p&gt;
&lt;p&gt;Then, came the belts. In Godot I used physics bodies for items on belts because that was the easiest way to do so but it came with a lot of cons the biggest of which being the fact that belts had no throughput limit. Which cuts out basically 80% of the puzzle in automation games.&lt;/p&gt;
&lt;p&gt;The tricky part about Factorio-style belts is that they need to be updated back to front, so I had to figure out which belts depend on which other belts – a mini dependency graph!&lt;br /&gt;
Now, after coming from Godot my perception of performance was so ruined that I was incredibly worried that this was going to be a major performance bottleneck if I didn&apos;t aggressively cache it.&lt;/p&gt;
&lt;p&gt;Luckily for me, I am not skilled enough to be able to write an algorithm that&apos;s able to heavily cache it so I decided to settle for just reconstructing the entire graph every frame. And guess what? It never became a performance problem.&lt;/p&gt;
&lt;p&gt;Well that was simple enough, I&apos;ll also mention the things that happened after all of that.&lt;br /&gt;
I went on a 3 week journey to optimize the heck out of the voxel engine, during which I switched from OpenGL to WGPU because OpenGL on MacOS does not support Shader Storage Buffer Objects (SSBOs)&lt;/p&gt;
&lt;p&gt;I might make a post on how I optimized the voxel engine (&lt;a href=&quot;https://www.youtube.com/watch?v=40JzyaOYJeY&quot;&gt;check out this video by Vercidium&lt;/a&gt;) but by the end of it I had a voxel engine with async chunk &amp;amp; mesh generation, running at 144FPS+ on an M2 MacBook using 3 GB of ram with a render distance of 3072 blocks in every direction.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://daymare.net/blogs/godot-ruined-me/assets/voxel-engine.png&quot; alt=&quot;Voxel Engine Picture&quot; /&gt;&lt;/p&gt;
&lt;h1&gt;The conclusion?&lt;/h1&gt;
&lt;p&gt;I can&apos;t say this story has a very good conclusion because the game never ended up getting finished. Mostly because I couldn&apos;t figure out a way to make a factory game feel nice to play in a 3D voxel sandbox.&lt;/p&gt;
&lt;p&gt;What I learned from all this wasn’t really about Godot or performance – it was about trust. Or the lack of it.&lt;/p&gt;
&lt;p&gt;I thought I was writing about slow code, but looking back, I was really writing about black boxes I couldn’t see inside.&lt;/p&gt;
&lt;p&gt;After going cold-turkey on abstractions, I realized something worse than inefficiency: bloat. It turns out working in a simple, honest codebase – one where you see everything – is just more fun.&lt;/p&gt;
&lt;p&gt;For those who may want to check out the source code for the final version of the game &lt;a href=&quot;https://github.com/todaymare/factory-game&quot;&gt;here you go&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Maybe there&apos;s a conclusion to draw from this story, in any case I hope you enjoyed it. And if you did, consider &lt;a href=&quot;https://ko-fi.com/todaymare&quot;&gt;buying me a cup of coffee&lt;/a&gt;&lt;/p&gt;
</content></entry><entry><title>Four years, Five failures, One compiler</title><id>https://daymare.net/blogs/four-years-five-failures-one-compiler</id><updated>2025-10-05T14:36:34.509273115+00:00</updated><author><name>daymare</name><uri>https://daymare.net/</uri></author><category term="BLOGS"/><link href="https://daymare.net/blogs/four-years-five-failures-one-compiler" rel="alternate"/><published>2025-10-05T14:36:34.509273115+00:00</published><summary>&lt;p&gt;At 14, I thought writing a compiler would be a quick side quest in building a game engine. Four years later, I finally built one that works. And it was one hell of a journey.&lt;/p&gt;</summary><content type="html">&lt;p&gt;At 14, I thought writing a compiler would be a quick side quest in building a game engine. Four years later, I finally built one that works. And it was one hell of a journey.&lt;/p&gt;
&lt;p&gt;Before I dive into the compilers themselves, I should explain why I even started making them in the first place. Initially, writing a programming language wasn&apos;t the goal; it was just supposed to be a small part of a bigger piece: building my own game engine. I didn&apos;t expect that &amp;quot;small part&amp;quot; to turn into a four-year rabbit hole, but now that you know why it all started, let&apos;s look at a few of the failed projects, shall we?&lt;/p&gt;
&lt;h2&gt;CryScript&lt;/h2&gt;
&lt;p&gt;Super edgy name, I know. To be fair, the game engine this was meant for was called &lt;em&gt;Crytex&lt;/em&gt;, so that doesn&apos;t make it any better. It was back in early 2022, so technically, the title of this post is clickbait. Oh well.&lt;/p&gt;
&lt;p&gt;While writing this, I dug through &lt;a href=&quot;https://github.com/todaymare/CryScript/&quot;&gt;the old GitHub repo&lt;/a&gt;, which had a very odd structure. Something like this:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-rust&quot;&gt;crates/
 cry_script/
 src/
        ..
    Cargo.toml
src/
 main.rs
Cargo.toml
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;I have no idea what I was on when I set it up like that, but for a moment, it actually scared me. I thought I&apos;d lost the source. Luckily, the code&apos;s all still there, so let&apos;s take a look and have some fun.&lt;/p&gt;
&lt;p&gt;I&apos;m pretty sure I started off by following a &lt;a href=&quot;https://www.youtube.com/watch?v=Eythq9848Fg&amp;amp;list=PLZQftyCk7_SdoVexSmwy_tBgs7P0b97yD&quot;&gt;Python compiler tutorial&lt;/a&gt;... in Rust.&lt;/p&gt;
&lt;p&gt;And surprisingly, that worked well for the first half. I got the lexer and parser running (basically the parts that read the code), and they still hold up well even now.&lt;/p&gt;
&lt;p&gt;However, things started falling apart once I reached the interpreter, the bit that&apos;s supposed to run the code.&lt;/p&gt;
&lt;p&gt;But up until that point? Honestly, not bad. Even skimming through it today, I can see the same patterns I still use in my work.&lt;/p&gt;
&lt;p&gt;..Oh my god, I take all of that back.&lt;br /&gt;
The interpreter is an abomination of Rust code, and it&apos;s also where you can clearly see the effects of Python. It&apos;s a tree-walk interpreter, which basically means the Rust compiler absolutely hates it.&lt;/p&gt;
&lt;p&gt;I vividly remember fighting the borrow checker &amp;amp; sprinkling in `Rc&amp;lt;RefCell&amp;lt;_&amp;gt;&amp;gt;&apos;s everywhere whilst writing this, and it seems like eventually, I just gave up and resorted to doing:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-rust&quot;&gt;struct VariableReference {
 reference: *mut Variable,
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Which is, apparently, a non-clone, manual version of a reference-counted value?&lt;br /&gt;
Not a clue who wrote that code; definitely wasn&apos;t me!&lt;br /&gt;
For non-Rust users: this is me fighting the language&apos;s safety system and losing badly.&lt;/p&gt;
&lt;p&gt;I have no idea where this was needed, but it might be one of the worst pieces of Rust code I&apos;ve ever written, second only to the &lt;em&gt;Crytex&lt;/em&gt; engine.&lt;/p&gt;
&lt;p&gt;That one doesn&apos;t even compile anymore, by the way, because newer Rust versions prevent casting an immutable reference to a mutable one. (Which, fun fact, is undefined behaviour)&lt;/p&gt;
&lt;p&gt;Oh, and apparently, I was passing around the main context of the interpreter as a mutable pointer. Those of you who use languages like C or C++ might think &amp;quot;so what? that&apos;s normal&amp;quot;, but in Rust, using pointers mixed with references comes with quite a bit more headache.&lt;/p&gt;
&lt;p&gt;In hindsight, &lt;em&gt;CryScript&lt;/em&gt; was less of a compiler and more of a Rust safety demonstration. The fact that it even ran code at all is a miracle. If I pulled this at a Rust conference, I&apos;d probably get banned. But it was a Rust newbie&apos;s first attempt at making a compiler, and it wasn&apos;t a half-bad one.&lt;/p&gt;
&lt;h2&gt;azurite&lt;/h2&gt;
&lt;p&gt;Fast forward a year, and I started making &lt;em&gt;azurite&lt;/em&gt; (the &apos;a&apos; is non-capitalised intentionally). This one&apos;s very near and dear to my heart as it&apos;s the project that let me meet some amazing people I still talk to today.&lt;/p&gt;
&lt;p&gt;Unlike &lt;em&gt;CryScript&lt;/em&gt;, this one came a bit later, when I actually knew how to use GitHub. It has 76 commits, many of which have completely nonsensical names that could probably get me rejected from a few jobs.&lt;/p&gt;
&lt;p&gt;I should, however, mention that between starting &lt;em&gt;azurite&lt;/em&gt; and finishing &lt;em&gt;CryScript&lt;/em&gt;, I joined the r/ProgrammingLanguage&apos;s Discord server, which led to me reading the book &lt;a href=&quot;https://craftinginterpreters.com/&quot;&gt;Crafting Interpreters&lt;/a&gt;. I think everyone interested in compilers or interpreters should go through it at least once.&lt;/p&gt;
&lt;p&gt;Unfortunately, I don&apos;t actually know when &lt;em&gt;azurite&lt;/em&gt; started, since the GitHub repo seems to have been created after the language was already up and running. There are a bunch of example programs and documentation in there, so it must&apos;ve been pretty far along.&lt;/p&gt;
&lt;p&gt;There are binaries uploaded from the early days, but since I apparently thought my code was too special to share, I didn&apos;t upload the source, you know, in case someone stole my brainchild. After a while, though, I came to my senses, uploaded the code, and blessed the world with my amazing engineering.&lt;/p&gt;
&lt;p&gt;A significant difference between &lt;em&gt;azurite&lt;/em&gt; and &lt;em&gt;CryScript&lt;/em&gt; is that &lt;em&gt;azurite&lt;/em&gt; actually had static analysis, whereas &lt;em&gt;CryScript&lt;/em&gt; was dynamically-typed. That said, the lexer and the parser are almost identical.&lt;/p&gt;
&lt;p&gt;I&apos;d love to make fun of the static analysis, but honestly, many of the core ideas I used back then are still part of how I think about language design today. It&apos;s surprising to see that, at least on a high level, I already had a so-so grasp of the basics.&lt;/p&gt;
&lt;p&gt;But I did find this comment, which I think perfectly captures my ambition at the time.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;// TODO: Maybe make the multi-file-loading multi-threaded
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The bytecode interpreter for &lt;em&gt;azurite&lt;/em&gt; had a major performance flaw that someone else figured out. If I remember correctly, it was something like this:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-rust&quot;&gt;let mut callstack = ..
let mut code = &amp;amp;mut callstack[0];
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;vs&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-rust&quot;&gt;let mut code = callstack[0].clone();
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;That one change made the interpreter run &lt;strong&gt;10x faster&lt;/strong&gt;. Wild. But more importantly, that flaw introduced me to &lt;a href=&quot;https://www.youtube.com/@leddoo&quot;&gt;leddoo&lt;/a&gt;, who&apos;s now a close friend. So I guess being bad at programming has its perks.&lt;/p&gt;
&lt;p&gt;I also noticed other people starring and contributing to this project, which might make you wonder why I stopped working on it. The reason is quite simple really, the codebase collapsed under its own weight.&lt;/p&gt;
&lt;p&gt;See, that&apos;s the problem with making a long-term project in a field you barely understand. Every time you add something that wasn&apos;t initially expected, in my case that was generics, the codebase just gets worse and worse and eventually it got too much to handle.&lt;/p&gt;
&lt;p&gt;It&apos;s bittersweet reading the old commit log. People added features like a REPL — that one was my now-friend &lt;a href=&quot;https://github.com/pyrotek45/&quot;&gt;Pyrotek45&lt;/a&gt; — and then months later, I removed it. To anyone who ever contributed to &lt;em&gt;azurite&lt;/em&gt; and happens to read this: thank you. I really mean it.&lt;/p&gt;
&lt;h2&gt;margarine&lt;/h2&gt;
&lt;p&gt;And now the final gauntlet. &lt;em&gt;margarine&lt;/em&gt;.&lt;br /&gt;
I started near the end of 2023. At first, it was supposed to be for another game engine, this one called &lt;em&gt;butter&lt;/em&gt;. The plan was to make a language centred around ECS architecture and value types. I even wrote a lexer, parser, semantic analysis, and an LLVM-based codegen.&lt;/p&gt;
&lt;p&gt;But when it came to integrating it, I caved and just used Lua. Yeah. Because here&apos;s the truth: I&apos;d spent four years learning how to make a compiler... but not how to make a game engine.&lt;/p&gt;
&lt;p&gt;That actually felt horrible. After all that work, I shelved margarine. I moved on to voxel engines, games, raytracers, fluid simulations and much more..&lt;/p&gt;
&lt;p&gt;You read the title, though, you know the story didn&apos;t end there.&lt;/p&gt;
&lt;p&gt;A couple of weeks ago, I came back to &lt;em&gt;margarine&lt;/em&gt;, for what reason I don&apos;t know. This time, I stripped away the over-engineering (no more ECS gimmick). I ditched LLVM (way too painful) and built a clean bytecode interpreter instead.&lt;/p&gt;
&lt;p&gt;And now? I finally have what I dreamed of: an embeddable programming language I can use in any project.&lt;/p&gt;
&lt;p&gt;So here it is, after years of broken interpreters, pointer hacks, and abandoned repos, I am delighted to introduce to you, &lt;em&gt;margarine&lt;/em&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-rust&quot;&gt;fn main() {
 var numbers = [1, 2, 3, 4, 5]
 var multiplier = int::parse(std::read()!)!

 var doubled = numbers.map(|n| n * multiplier)
    print(doubled)
    // If input is 2 → [2, 4, 6, 8, 10]
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;A language that feels like Rust, but without the fights I used to lose with the borrow checker. It&apos;s a statically-typed language that is almost too trivial to embed into any project. The syntax is very akin to Rust with Iterators, Closures, and much more!&lt;/p&gt;
&lt;p&gt;Was this post just a big ad for &lt;em&gt;margarine&lt;/em&gt;? It certainly wasn&apos;t the intention but after four years it feels good to finally share something that works, and it couldn&apos;t hurt to &lt;a href=&quot;https://github.com/todaymare/margarine&quot;&gt;check it out&lt;/a&gt; can it?&lt;/p&gt;
&lt;p&gt;and hey, if it resonated with you &lt;a href=&quot;https://ko-fi.com/todaymare&quot;&gt;consider dropping me a coffee&lt;/a&gt;&lt;/p&gt;
</content></entry><entry><title>hello world</title><id>https://daymare.net/blogs/first-blog</id><updated>2025-10-02T19:36:48.068611622+00:00</updated><author><name>daymare</name><uri>https://daymare.net/</uri></author><category term="BLOGS"/><link href="https://daymare.net/blogs/first-blog" rel="alternate"/><published>2025-10-02T19:36:48.068611622+00:00</published><summary>&lt;p&gt;uh, hi, idk what to say here but helloooo. i made a static site generator for this. it takes the first line of the markdown as the title and the 2nd line as the summary actually i lied you can use multiple lines if you end the line with a \&lt;/p&gt;</summary><content type="html">&lt;p&gt;uh, hi, idk what to say here but helloooo. i made a static site generator for this. &lt;br /&gt;
it takes the first line of the markdown as the title and the 2nd line as the summary &lt;br /&gt;
actually i lied you can use multiple lines if you end the line with a \&lt;/p&gt;
&lt;p&gt;um, i&apos;m gonna ask chatgpt to write something here&lt;br /&gt;
it generated something super corny as expected. i ain&apos;t putting that in here&lt;/p&gt;
</content></entry></feed>