GeistHaus
log in · sign up

https://feeds.feedburner.com/mihailco

atom
85 posts
Polling state
Status active
Last polled May 19, 2026 04:34 UTC
Next poll May 20, 2026 04:23 UTC
Poll interval 86400s
Last-Modified Sun, 10 May 2026 22:00:44 GMT

Posts

FreeDOS on a USB stick
Show full content
FreeDOS on a USB stick

I looked around and couldn’t find any reasonably good guide to install FreeDOS on a USB stick with persistence. So, I thought to myself that I’d write something up, because why not?

Now, why on earth would one want a USB stick with FreeDOS? I don’t know, perhaps one doesn’t really need any particular reason. You can just do things! Haven’t you heard?

Installation

Anyway, let’s get down to it. I’ll be using a rather ancient Kingston USB stick clocking at a whopping 2GB of available storage.

kingston_2gb

What a beaut, right? Brings back to late 2007 and early 2008 vibes, Way back when all those snotty nosed brats in tweed jackets got all us screwed over. Those were the good old days. Aye?

Other than the stick at hand, we’ll need QEMU, and the latest version of FreeDOS, which happens to be 1.4 at the time of me writing this down.

$ mkdir freedos
$ cd freedos
$ wget https://www.ibiblio.org/pub/micro/pc-stuff/freedos/files/distributions/1.4/FD14-LiveCD.zip
$ unzip FD14-LiveCD.zip

This should result in a lovely ISO file called FD14LIVE.iso in the current directory.

Now it’s time to boot it up. Please make sure to replace /dev/sdX with the device that corresponds to your USB stick, when running the incantations outlined below.

$ sudo -E qemu-system-i386 \
	-cpu pentium \
	-m 32 \
	-vga cirrus \
	-drive file=/dev/sdX,format=raw \
	-cdrom `pwd`/FD14LIVE.iso \
	-boot d

If everything went according to plan, you should be greeted with a screen that looks something like the one below.

01

You’ll want to pick the first option, namely: Use FreeDOS 1.4 in a Live Environment mode.

The next step is to partition, and format the disk with fdisk. This could have been done beforehand of course, with a slightly more comfortable tool like GParted, but where’s the fun in that? We are very bullish-10x-LLM-augmented-DOS-engineers here, am I right?

To do so, simply type fdisk and take it from there. It all should be pretty self explanatory. I decided to have two partitions, a smaller C: for the OS and D: using up all the remaining space for everything else.

Make sure to toggle the bootable flag on the C: partition.

02

Finally, type shutdown, then run qemu-system-i386 again, making sure to pick the first option once more, then it’s time to format both partitions by typing in the following incantations:

format C:
format D:
shutdown

03

04

Run qemu-system-i386 for the third time, picking the Install to harrdisk this time around.

05

06

07

08

09

10

11

12

13

14

At end of the installation, make sure to pick Yes - Please reboot now, and then subsequently Boot from system harddisk

15

16

If all went well you should find yourself at a C:\> prompt. You’ll want to type shutdown for the last time, and boot it all up again, making sure to adjust the incantation to -boot from the C: partition instead.

$ sudo -E qemu-system-i386 \
	-cpu pentium \
	-m 32 \
	-vga cirrus \
	-drive file=/dev/sdX,format=raw \
	-boot c

Now, let’s have a little chat about the free space situation. There’s around 535MB free space left on C:, and we got all the free space in the world on D:, or 1.2GB to be more exact.

Who wouldn’t have killed for a 2GB hard-drive back in the day, am I right? I was fortunate enough not to have to endure the pain of 20MB and 40MB hard-drives, as my very first PC had a 540MB one.

Bonus CD

You’ll very likely want to grab the so called Bonus CD, and install even more software, which you can do fairly easily simply by executing the following incantations:

$ wget https://www.ibiblio.org/pub/micro/pc-stuff/freedos/files/distributions/1.4/FD14-BonusCD.zip
$ unzip FD14-BonusCD.zip
$ sudo -E qemu-system-i386 \
	-cpu pentium \
	-m 32 \
	-vga cirrus \
	-drive file=/dev/sdX,format=raw \
	-cdrom `pwd`/FD14BNS.iso \
	-boot c

I can never remember the name of the package manager for the life of me. It’s called FDIMPLES, which rhymes with PIMPLES, but my brain simply refuses to cooperate with me when it comes to recalling it.

17

18

19

Based on the things that I have selected to install, I was left with 388MB on the C: partition after it was all said and done.

20

What did you pick? Decisions, decisions.

Extras

If you are like me, then you’ll probably want to add in even more extras into the mix, because life is way too short without some spice and excitement.

Name Version Type License Download Catacomb 3-D Abyss 1.13 Game Shareware catabs13.zip Wolfenstein 3-D 1.4g Game Shareware 1wolf14.zip DOOM 1.92 Game Shareware doom19s.zip Commander Keen: Episode VI Promo Game Shareware keen6promo.zip Tomb Raider Part 1 Game Demo tombdemo.zip Tomb Raider Part 2 Game Demo tombraid.zip Cracker Editor 1.16a Utility Freeware cre116a.zip PictView 1.94 Utility Freeware pictview.zip Norton Commander 5.50 Utility Abandonware nc.zip Borland C++ 3.0 IDE Abandonware bcpp.zip Turbo C++ 3.0 IDE Abandonware tbcpp.zip Turbo Pascal 7.01 IDE Abandonware tp7.zip

I ended up dropping these on the D: partition inside an aptly named SETUP directory, and then installing them from there to C: as one would from an actual floppy disk image.

The SETUP files for these will amount to about 45MB in total when unpacked.

RAM Disk

If you have embarked on this journey, you probably are going to be running FreeDOS on a system that has plenty of RAM to spare; way more in fact that you ever could have dreamed off during the days of old, and therefore spinning up a RAM Disk or two is generally just what the doctor ordered.

Luckily, this is relatively painless to do with FreeDOS, and it comes down to using RDISK.

In FDCONFIG.SYS:

23?DEVICEHIGH=C:\FreeDOS\BIN\RDISK.COM /S64 /:E

And, then in FDAUTO.BAT:

IF EXIST E:\NUL MKDIR E:\TEMP
IF EXIST E:\TEMP\NUL SET TEMP=E:\TEMP
IF EXIST E:\TEMP\NUL SET TMP=E:\TEMP

Finally, if let’s say you wanted to run and play WOLF3D from the RAM disk, you’d do:

xcopy C:\WOLF3D E:\WOLF3D /I /S /E
E:
cd WOLF3D
WOLF3D.EXE
Snapshot

At this point, it might be a good idea to take a full snapshot of the drive for safe keeping:

$ sudo dd if=/dev/sdX of=usbstick.img bs=1M

This snapshot then can be restored at a later time, by simply running the following incantation:

$ sudo dd if=usbstick.img of=/dev/sdX bs=1M

Plus, you won’t have to go through the pain of installing and configuring everything if something gets all tangled up after a botched install.

The End

The nice thing about this setup is that both partitions are good old regular FAT32, so one can just copy to/from freely, without having to go through QEMU or use any other special disk imaging utility.

Some of you might be asking right now, why didn’t I just create an empty .img, install to that via QEMU, then use dd to perform a raw copy to the USB stick with the following incantations:

$ qemu-img create usbstick.img 2G
$ qemu-system-i386 <...>
$ dd if=usbstick.img of=/dev/sdX bs=1M

Wouldn’t that also been faster? The answer to that question is as always a resounding: “It depends!”

To paraphrase a very dear acquaintance of mine with excellent taste in food and expert level culinary skills: “After all, as I am sure your mother tells you, and most certainly told me, it’s always important to try new things”.

If you are in the mood for having some real period appropriate fun, then you could try following this guide by Fabien Sanglard, and take a stab at compiling Wolfenstein 3-D from source, the way it was intended way back in the great year of 1992.

https://mihail.co/2026/05/10/freedos-usb-stick/
Does your blog still render in IE6?
Show full content
Does your blog still render in IE6?

I got really curious a short while back, if this very blog would still be somewhat readable in the crown prince of terrible browsers, also known as IE6. And, while I was contemplating that evil thought, I said to myself what about others like Mozilla 1.5 or Netscape 6?

IIS

While this blog is statically generated, it still needs a local web server to be browse-able, due to the directory structure that provides the so called pretty-permalinks.

No permalinks that end with /index.html here. Oh no, no! Not on my watch.

Now, there’s absolutely no better choice than IIS for this very purpose, right? Besides, a bastardized (personal?) version of it is available right under our very own finger tips, via the infamous Add or Remove Windows Components”.

iis

Then, I dropped all the files into C:\Inetpub\wwwroot, and then added index.html to the list of files recognized as a Default Document.

iisdefdoc

Microsoft Peer Web Services

pws

It turns out that Microsoft used to ship an utter monstrosity called Peer Web Services, which was supposed to be the equivalent of IIS for Win9x.

The more you know!

With all that out of the way, it was time to fire up each browser, and take a look.

IE6

ie6

Mozilla 1.5

moz15

Netscape 6

ns6

The “Final” Verdict

I would be lying if I didn’t mention that I did end up making a couple of minor tweaks.

First and foremost, I added a couple of additional font-families like Verdana and Tahoma to make the fonts somewhat more palatable, and a tiny bit easier on the eyes.

Then continued with adding render-fallbacks inside the <video> and <audio> elements (tags?), so that they render a link that can be used to download and view the media.

<video muted controls width="1024">
    <source src="ie6.mp4" type="video/mp4">
    <blockquote>
    Your browser does not support the video element.
    Please click <a href="ie6.mp4">here</a> in order to
    download and view the video.
    </blockquote>
</video>

But overall, everything is pretty much readable at a 1024x768 resolution. What about that very annoying horizontal scrollbar? I really wasn’t in the mood of doing more layout sizing and padding kung-fu in order to make it all fit inside a 1024px wide viewport.

Sorry, not sorry!

https://mihail.co/2026/05/02/does-your-blog-still-render-in-ie6/
2026 April Retrospective
Show full content
2026 April Retrospective

This month we got to hear the first gong that signals the beginning of the end of what I like to call the infinitely subsidized token era. Uh-oh! Rooh-rah!

What does this really mean in the long term? Well, it remains to be seen, but in my opinion there’s a pretty high chance that only a handful of the so called frontier-labs will survive in the end.

Which ones? You might be tempted to ask. Well, two of them are ad-businesses, that subsidize their entire product lines already, so in theory they should be able to continue doing the same with tokens as well, pretty much indefinitely if necessary.

I am referring to none other than mighty Google, and Facebook of course!

Micro Cornucopia

Look at this absolutely splendid issue (No. 41) of Micro Cornucopia all the way back from May 1988.

mcc

Neural Networks: Modeling human reasoning is the first step in creating a useful robot.

The more things change, the more they same. At any rate, we are not on LinkedIn over here, so that was more than enough yapping about AI for the time being.

Insidious: Out of the Further

insidious_further-fs8

I really thought that we might not get another entry in the Insidious franchise, and boy I am glad that I was dead wrong when it comes to this one.

This is going to be an absolute banger, I can feel it in my old wretched bones already.

GitHub Enshitification (Part Deux)

I was moaning about the enshitification of GitHub last month, and boy things have gotten a whole lot worse since then. I mean, we are now approaching some sort of a tipping point, where utter incompetence, and corporate brain-rot happen to be working in tandem.

badgh

The latest incident revealed a tiny little technical detail that made a lot of the previous ridiculousness make total and perfect sense. How so? Well, it was revealed that Elasticsearch is being leveraged behind the scenes, and that data was being re-indexed into it, which of course usually will take a while.

When I read all that, I was like: “Oh, so that’s why you can only look at the first 2000 issues!”, then I almost immediately followed up with: “Oh, that’s why you see the total count of issues or pull requests update, but the actual list takes a while before it’s updated!”.

Elasticsearch has been designed, well for search. Shocking, I know. Would could have guessed, right? And, it’s whole-fully unsuited for workloads where one needs to paginate a lot or needs the data to be fresh in close to real-time fashion.

In the end, there’s something good or fun to be had in everything, I guess.

Stronghold Crusader: Definitive Edition

scdlc26spring

The aforementioned spring DLC is upon us. It landed way sooner than I expected to be perfectly honest.

May your granaries never run out of food.

Vampire Crawlers: The Turbo Wildcard from Vampire Survivors

vc

That is quite a mouthful, isn’t it? I mean it’s very typical Poncle in every sense of the word.

I feel like, many were wondering about whether Poncle will also turn out to be some sort of like a one-game-pony studio or not. This is a fairly common phenomenon among the indie-crowd, so the doubt was all justified as far as I am concerned.

However, I am happy to report that the game is super fun, and I am saying that as someone who doesn’t like turn-based and card-based anything at all. But, this bad-boi manages to trick my primitive monkey-brain somehow, and for that I must tip my hat.

What about the numbers? Over 800_000 units sold according to Gamalytic. Take that naysayers!

Game Pricing

The question of game pricing has become like some sort of a seasonal flu it appears to me; and seems to resurface with a fairly regular cadence in all circles, be it indie, AA or AAA.

In more recent times the number 14,99 has been elected as some sort of an upper limit for the celebrity-indie crowd, and as the entry-level lower limit for the AA club.

Now take a look at this beautifully preserved rendition of Cyparade, the creators of one of my all time favorite games from the early 2000s, called Ballance.

balance

What do you notice? Our favorite number 14,99 (euro), in the great year of 2004 no less; which when adjusted for inflation, would turn out to be something in the range of 23,77 (euro) or thereabouts.

I hate being overly repetitious, but once again, I have to call out the fact that the more things change, the more they stay the same. Enough said!

s&box

sbox

S&box has finally made it out the door this month.

Last time, I was talking about S&box though, I did mention how based on one of the updates it did seem like Erin Catto was cooking up a 3D physics engine in the spirit of Box2D.

This has been officially confirmed now by none other than the good man himself.

box3d

Music to my ears all the way through.

SameGame

I decided to dust off and show off yet another little thingamajig from my ancient forgotten archives. Keep finding these as I sift through them, and will likely share more as the memories of the olden days kick in more than usual these days.

samegame

This is not as bad as I remember it being, but it’s nothing to write home about either.

$ winedump samegame.exe | grep TimeDateStamp
  TimeDateStamp:                44F15220 (Sun Aug 27 11:04:48 2006)

Twenty years gone in an instant, huh? Now, it’s worth calling out that I have borrowed (ahem!) the sprites from the SameGame clone that used to ship with GNOME 2.

herring3

Look at this unedited mess. What would anyone do without a highly meretricious soup of globals mixed with a healthy dose of Hungarian notation.

void Render( HWND hWnd )
{
	RECT rc;

	HDC  hDC = GetDC( hWnd );
	GetClientRect( hWnd, &rc );

	//FillRect(g_hDCMem,&rc,(HBRUSH) GetStockObject( BLACK_BRUSH ) );

	tileboard_Render(g_hDCMem);

	BufferSwap( hDC, rc );

	ReleaseDC( hWnd, hDC );
}

But hey, I can always throw in the excuse that goes something like: “I was young!”. Right? Besides, I don’t even remember, why on earth did I build this.

Plus, by the looks of it I was using LCC and not MinGW as a compiler. No idea why!

# Project: Makefile Template for the LCC compiler
# Makefile Created by The Icebreaker

LCC      = d:/DevTools/lcc/bin/lcc.exe
LCCLNK   = d:/DevTools/lcc/bin/lcclnk.exe
LCCRC    = d:/DevTools/lcc/bin/lrc.exe
RES      = samegame.res
OBJ      = samegame.obj $(RES)
BIN      = samegame.exe
LNKFLAGS = -subsystem Windows  
RM       = rm -f

.PHONY: all all-before all-after clean clean-custom

all: all-before samegame.exe all-after

clean: clean-custom
	${RM} $(OBJ) $(BIN)

$(BIN): $(OBJ)
	$(LCCLNK) $(LNKFLAGS) $(OBJ)

samegame.obj: samegame.c samegame.h
	$(LCC) samegame.c 

samegame.res: samegame.rc 
	$(LCCRC) samegame.rc
The “Sackhoff” Show

starbuck

At roughly four episodes a month, this is going to take a while.

Monthly “Layoffs Report

yogibear

The layoffs continue in full force, and at the time of me writing this the glorious number of 92_272 can be seen on Layoffs.fyi. That’s an increase of roughly 20_825 compared to last month’s report.

There goes the entire population of a small town. Poof gone, like Houdini.

Monthly “Amazon Book Review

This is yet another book from my personal collection and by a fellow Romanian author, Vasile Lungu. I have a physical copy of the first edition (the one on the left in dark blue).

I never realized that this got an updated second edition, and that it was subsequently translated into English in 2005 or so; and even less so that it was available on Amazon for a while.

limbasm

It’s out of stock now, which of course shouldn’t really surprise anybody.

limbasmarev

Why did I buy this? Well, I used to lurk in the bookstores in the so called “IT&C” section, whenever I got a chance really. The selection of books was always lackluster, and there weren’t a great many copies available either, considering that this was and still is a relatively small town.

So, I liked to be early and enjoy the spoils. But on a more serious note, I saw that it had a fairly complete interrupt reference, plus a chapter or so about MMX, and those two were enough to make me buy it at the time.

Both of those subjects were totally obsolete by 2003, since people have moved on from DOS and MMX was overshadowed by SSE already by that time, but hey beggars can’t be choosers.

Monthly “Coup de cœur”

Are you ready for another brutal awesome-sauce-fest from the Mekka & Symposium 2000?

If the answer to that question is a resounding “fuck yes!”, then please awe a gander at this absolute unit of a demo called Nature Suxx by none other than Federation Against Nature.

Please enjoy the show, and don’t forget to try the fish!

https://mihail.co/2026/04/30/april-retrospective/
Cracker Editor
Show full content
Cracker Editor

The Hungarian edition of the Chip Magazine used to ship semi-regular dumps of sac (Slovak Antivirus Center) archive, which was nothing short of a treastrove composed of copious amounts of freeware and shareware, among a variety of other things like documentation.

chip96dec

The whole collection used to be around 500 megabytes in the mid 90s, which of course would have been unphantomably massive for me to be able to attempt to download and store on my own. Not to mention absolutely prohibitively expensive.

Now, I have completely forgotten about it all, until relatively recently when I started dumping my aging CD collection into ISO, and noticed the sac directory while browsing one of them.

I quickly went to see if it’s still up and running, and to my surprise it was. Then, I went to my favorite category called UTILPROG, and it didn’t an awful long time before something that I don’t ever remember seeing caught my eye. What was it, you might ask?

A rather curiously named cre116a.zip, with the following description: Cracker Editor v1.16a - DOS HEX-Editor. I just knew that I had to try it out right there and then in DOSBox. Well, what can I tell you? I most certainly wasn’t dissapointed, that much is absolutely certain.

Since I know that you are dying to see how it looks, I won’t hold the suspense for much longer.

browse

When you run it, you are greeted by this wonderfully colored and rather trippy file browser. This caught me off guard for a hot second, I have to confess. I don’t really know how to explain it, but it most definitely has a certain rather unique charm to it.

Then of course, after choosing a file it gets even more colorful and stylish. Tell me that this is not the most beautiful things you’ve seen in your entire life. Come on now, tell the truth and shame the devil, like my good old friend Andre Linoge used to say.

search

I really like how it displays pretty much all the relevant metadata in a single screen. The search dialog is simple, but has case sensitive and wildcard search options, as no doubt can be seen in the screenshot.

What else? It also has your usual decimal to hexadecimal and vice-versa converter.

dec2hex

And last, but not least the quintessential ASCII table, which cannot be missing from any hex editor.

ascii

It also has a bunch of other features like: a calculator, a DOS shell, goto to a given offset, and inserting a NOP (0x90) at the current offset among other features.

One thing however, that I found to be lacking is the ability to switch to a non-hex-view for text files at least. I know, I know that that not every hex-editor can be as feature rich as HIEW, but still.

doc

Alas, not much can be found on the great information super-highway about the author, who happens to be going by the name of Toni Mäkelä, and rather unsurprisiginly appears to be from Finland.

The URL http://www.sci.fi/~makton/cre/ listed in CRE.DOC cannot be found on archive.org either, which makes sense considering that the year was 1996; which is exactly the year when the Internet Archive was founded.

At any rate, I consider it to be a true hidden gem that have survived well into the Windows era, and besides 16 colors should be enough for everyone, right? Wink, wink.

https://mihail.co/2026/04/12/cracker-editor/
2026 March Retrospective
Show full content
2026 March Retrospective

This month was pretty much dominated by war, death, sex, drugs, layoffs, DLSS 5, and last but not least quite a few highly anticipated indie-game releases.

But, isn’t this actually the case every single month nowadays? A fair point my liege.

DLSS 5

jensen

Let’s start with the elephant in the room, just to get it out of the way. Personally, I couldn’t care less about DLSS or any other related technology, simply because realism is not something that interests me in the slightest when it comes to video games or movies for that matter.

While I love eye-candy as much as the next person out there, I never ever play anything simply due to its realism or graphical fidelity. It’s just not my thing, and it never was. However, unlike others out there, I have no quarrel with people who do and very much expect games to deliver on this front.

The last thing I am going to say on this matter is that if people don’t want DLSS 5 to become a thing, then it’s time they start shipping games (and game engines!), that work just fine on 15 years old hardware.

The “Sackhoff” Show

starbuck

Chop Shop

chopshop

Chop Shop has been released, and I feel like it didn’t really get the attention that it deserved. But, I am biased because like I said when I first mentioned it, I happen to know one half of the team.

Look at these absolutely gorgeous achievement icons. This alone should convince you to buy it.

chopshopachievements

Here is my review of the game that I have posted on Steam.

The best way to describe this game is by imagining that in some alternate timeline Dave the Diver and Shank had a baby. It has a very unique look and feel, and the controls are pretty tight as well.

Even though this particular sub-genre is not my usual cup of tea, I still felt engaged, while I played through the first couple of levels.

Last but not least, there’s a lot of attention to detail, and one can see that this is nothing short of a labor of pure and concentrated love.

Go play it. Chop, chop!

Mewgenics

mewgenics

Mewgenics has been one of the most anticipated games out there. While not my personal cup of tea, I still wanted to mention it as it’s an Edmund McMillen game.

I find it funny how people were surprised that it sold over 150_000 copies in the first 6 hours.

Slay the Spire 2

slaythespire2

Slay the Spire 2 has been yet another highly anticipated title, that fans of the first installment have been ardently waiting for the past couple of years.

I am just not fan of this genre. Please don’t try to take a shot every single time I say that.

Tangy TD

tangytd

Tangy TD is a tower defense game built by a game-dev-twitch-streamer (that was quite a mouthful, wasn’t it?), who also happens to be known on the great information super-highway as Cakez77.

I was pleasantly surprised that it made it over the finish line to be perfectly honest, and then it went viral a couple of times after launch, which helped with the sales quite a bit.

Stronghold Crusader: Definitive Edition

scde

Didn’t I call it last time? Yes, I did. It would have been absolutely silly not to bring out more content when the game is still relatively hot.

Slug

slug

Slug by Eric Lengyel has been set free this month; both in terms of the patent and the relevant reference implementations of the shaders that power it under the hood.

The game-dev community took no time at all to embrace it all, and various implementations have popped up quicker than mushrooms after a warm spring rain.

Due to its reliance on partial derivatives (read: fwidth) and integer math, which could lead to all sorts of weird issues on older hardware, I wouldn’t personally recommend it despite all its clear advantages.

GitHub

When GitHub ended up without an actual CEO, and ceased to be a somewhat independent organization within the Microsoft umbrella, I didn’t think for a second that the enshitification of it would hit as fast as it did. Every single day, it becomes more unstable, and unusable.

ghstatus

A grand total of 32 incidents during the month of March. GitHub is almost cooked as far as I am concerned, and I say that as someone who absolutely loves the platform.

Signed up on 2009-02-23T12:49:46Z and been a paying user since 2010 or thereabouts. Too bad!

Tribal Trouble

I found out about Tribal Trouble, way back in 2004 after I stumbled upon a paper that is titled “Realtime Procedural Terrain Generation”, which just so it happened to be authored by one if its creators.

Why am I talking about it now? Well, it occurred to me that its source code was open sourced at some point, but I couldn’t really remember when exactly. When I went to check it out, I found out that the year was 2014, which made me feel old yet again.

The curiosity got better of me, and decided to try to compile it. How hard that can be? This is Java, which you write once and run everywhere, or so I’ve been told when I was young lad.

So, does it still build and run or what? It does, after a couple minor adjustments.

-<property name="jre" value="jre1.5.0_04"/>
+<property name="jre" value="jre8"/>

Is jre 1.5 in the room with us now? I hope not.

And then in common/classes/com/oddlabs/regclient/RegistrationClient.java:

public boolean isRegistered() {
-	return registration_info != null || registered_offline;
+	return true;
}

Could have also used the provided registration keyfile and avoid touching the source code at all, but then again where’s the fun in that? That’s not how we roll around here.

tt1

Voilà! It builds and runs as good as ever! The power of a nutritious cup of covfefe, right?

Now, it’s worth calling out that this is all good old-fashioned fixed-function pipeline, namely OpenGL 1.x with the multi-texturing extension, and at least two available texture units.

tt2

The terrain is also as lovely as I remember it. Using 666 as the map seed, produces this super tiny island, which can be fun for 2 players or total mayhem for more. Pick your poison!

mIRC + Winamp

Continuing with last months’ trip down the “what did I find in my archives” lane, I wanted to mention yet another silly little thing that I built way back in late 2006; which lay dormant in my archives waiting for me to stumble upon it once more in the year 2026.

herring2

Do you remember IRC and mIRC? They are still around and haven’t completed faded into obscurity, but one rarely hears anything at all about either of these anymore.

Way back in 2006 things looked very different, and IRC was still somewhat relevant despite the fact that the various messengers have severely eaten into its market-share, to say the least.

mirc

So what is this all about? It’s about a small DLL that can be used to control Winamp from mIRC.

How did it work? The helper mIRC script looked a little something like this:

; ...

menu channel,query,nicklist,menubar {
  -
  ircMP3 Status: winamp_status
  ircMP3 Play: winamp_play
  ; ...
  -
}

; ...

alias winamp_play {
  if ($dll("ircmp3.dll", winamp_status, NOT_USED) != 0) {
    $dll("ircmp3.dll", winamp_play, NOT_USED)
  }
  winamp_status
}

; ...

mircmp3

Then the DLL exported a bunch of functions like this:

// defined in `wa_ipc.h`
#define WINAMP_BUTTON2 40045

__declspec(dllexport) int __stdcall winamp_play(
    HWND mWnd,
    HWND aWnd,
    TCHAR *data,
    TCHAR *parms,
    BOOL show,
    BOOL nopause
)
{
	HWND hwnd = FindWindowA("Winamp v1.x", NULL);
  	if(hwnd != NULL)
    {
    	SendMessage(hwnd, WM_COMMAND, WINAMP_BUTTON2, 0);
    }
  	return 1;
}

For some reason or another, I didn’t end up using the LoadDll mechanism, which would have allowed me to avoid having to load the DLL every single time the mIRC script called $dll.

Oh, well. This was 20 whole years ago, and it’s all ancient history now.

Warning: do not use DLLs from sources you do not trust. See the Accepting Files section for information on the dangers of accepting and using files from the internet.

This gave me a little chuckle when I found out that this was still out there in the mIRC documentation.

Ahoy: AUG Paged Out: Issue #8

pagedout8

The new issue came out on the 26th of last month, but I managed to miss it somehow. Not subscribing to their RSS feed might or might not have had something to do with it.

Monthly “Layoffs Report

yogibear

I do realize that the numbers shown on Layoffs.fyi are nowhere near accurate, but 71_447 at the end of March is a pretty high number nonetheless.

These massive layoffs fuel the fires of socialist brainrot, and of course those whos’ minds have been completely taken over by it are more than happy to bring more people into the fold.

You can see this spreading all over the information super-highway like wildfire. Remember, like I said before, these recent waves have absolutely nothing to do with AI. Don’t let yourself be fooled.

Monthly “Amazon Book Review

I happen to be part of the tiny club of apostates, who haven’t read or owned what is now considered to be the quintessential, and must have C Progamming Language book written by the dynamic duo composed of the Bell Labs extraordinaires Brian Kernighan and Dennis Ritchie.

ccpp

I got the C: The Complete Reference and C++: The Complete Reference by Herbert Schildt instead.

If you thought that’s very weird or cringe, or something along the those lines, then wait until you hear that I’ve gotten the translated versions, since it was near impossible to get the originals in English at the time in this part of the world at least.

Plus, given the fact that even the translated editions were quite expensive at the time, I think that it’s rather fair to state the fact that I could have never afforded to import the originals from across the great pond anyway.

I was just a poor teenager from the former Eastern Bloc, ya’ know? The ugly specter of the regime change was still fresh in the air, despite the new millennium approaching at an ever so accelerated pace.

C: The Complete Reference Review

crev

C++: The Complete Reference Review

cpprev

Sadly, I couldn’t find any reviews from the 90s like last time, so these will have to do. They also aren’t exactly singing praises either for what it’s worth.

Monthly “Coup de cœur”

Coming at you this month with yet another banger called 954 by Apocalypse Inc from the very same and mighty Mekka & Symposium 2000 once more.

Please enjoy the show, and don’t forget to try the fish!

https://mihail.co/2026/03/31/march-retrospective/
2026 February Retrospective
Show full content
2026 February Retrospective

Let me start this off by saying yet again: we should really offset each day of the year by a tiny amount in order to have a February with proper 30 days. This nonsense has to stop somewhere. Imagine us as a multi-planetary species, doing shit like this across multiple planets; it’s gonna be a nightmare to deal with this crap. And, while we are at it, let’s just all use UTC only for crying out loud.

Februaries are always weird anyway, because they are the month when I grow older with one cycle, and because of this, I have decided to write a special Birthday Retrospective each year going forward.

Also, this month’s retrospective is packed with a metric ton of video content, due to the fact that there have been quite a few banger drops to the likes of which I wanted to draw special attention to.

The “Sackhoff” Show

When Katee “Starbuck” Sackhoff made a rather impromptu appearance on Joe Rogan at the tail of last year, it all really caught me totally by surprise, and I said as much at the time.

This time she’s back with a brand new series that it’s going to be an absolute banger.

You better get used to me posting about this until the very end. So say we all.

Escape the Chaos

Morcheeba’s latest album Escape the Chaos is totally as fresh as it can be, while still retaining that quintessential Morcheeba texture and feel that we all have grown accustomed to love and respect.

But as per usual, you don’t have to take my word for it. Have a listen and decide for yourself.

Hocus Focus: Demo

hf

Hocus Focus got a nice and shiny demo this month. Go and give it a try if you fancy these types of cozy games. I also feel like we are starting to reach the point where the term cozy game is starting to be overloaded, and people will soon start having arguments about the nuances of it all.

Alas, the SEO still hasn’t really picked up on the game yet, and we are roughly two months after the initial announcement. The ways of the great information super-highway are mysterious indeed.

Chop Shop: Demo

chopshop

Chop Shop is yet another indie game from another ex-colleague and friend of mine, and it got a demo this month. Since I know full well that people hate even the idea of the so called indie-solo-dev, you’ll be happy to know that it’s the product of a great dynamic duo.

Stronghold Crusader: Definitive Edition

If you thought that news about definitive edition of Stronghold Crusader will come to a complete and total halt in the new year. Well, I have some news for you. You were wrong!

The winter DLC has arrived, which is supposed to be the last DLC according to the official roadmap.

It remains to be seen whether this turns out to be the case or not, considering the game’s popularity and the complete revitalization of the community at large as well.

El desorden que dejas

While finding myself having to do a fresh install on my new box, I have realized that three of my long time utilities are pretty much out of comission, if not all out dead. Look, I am really not the one to shy away from compiling from source or pulling in something from the AUR, if and when the situation actually warrants it.

However, it’s becoming more and more clear that GTK delenda est, and all software that relies on it is bound to encounter a slow but rather painful death.

The utilities that I’m talking about here are as follows: volumeicon, viewnior, and last but not least nitrogen. Obviously, there are far more than these that will be affected in the not so remote future, but these are already difficult to build on modern systems due to their reliance on GTK.

If you take a look at their respective project pages or repositories, it’s obvious that even porting to GTK3 was simply far too much pain and effort, let alone anything newer than that.

While, I could most definitely fork them and try to migrate them all to something like GTK4, the amount of manual labor and effort that would take is simply too great, or far greater than rebuilding them from scratch with only the features that I rely on or care about.

I managed to hold off from moving away from libvte for my terminal emulator at the moment, but I just have to bite the bullet, and replace these with my own muy pronto, because I really have no patience anymore to constantly deal with this crap.

What the people building GTK don’t seem to quite understand is the fact that without the existence of an official libgtk2-compat, that happens to maintain full backwards compatibility while using GTK3 or GKT4 under the hood; the severe code rot and the extreme difficulty in terms of porting will lead to the deaths of countless projects similar to ones I listed above.

I know full well that I keep beating an already dead horse here, and largely preaching to the choir, but it’s extremely frustrating to having to constantly deal with this absolute hot mess, largely left behind by people who just don’t seem to understand the importance of backwards compatibility. It is sooooo very much frustrating, arrggh!

Exhibit A

Fortunately, there are some projects out there like SDL, built and maintained by people who understand backwards compatibility very well; so well in fact, that they basically release a companion compat library after each major release that contains breaking changes.

Awe a gander at sdl12-compat and sdl2-compat and bask in the glory of software well done.

/*
	SDL_SetRefreshRate was never in an real SDL-1.2 release,
	but apparently StepMania was maintaining a fork with this
	API for literally years.
*/
DECLSPEC12 void SDLCALL SDL_SetRefreshRate(int rate)
{
    /* takes effect on next SDL_SetVideoMode call. */
    DesiredRefreshRate = (rate >= 0) ? rate : 0;
}

This is exactly what one would expect from the likes of GTK. Oh well!

Stardew Valley 10-year Anniversary Video

While I am not a fan of this particular genre, which shouldn’t come as a surprise really, I still wanted to call this one out. Simply because, Eric Barone should be heralded as an example in my honest opinion, when it comes to how one should keep a game that is not primarily a live-service game, continue to feel alive and remain quite fresh for a decade at this point.

Seeing how the game evolved over the years in the video above, should be more than enough to convince anybody of all the hard work and sweat that went into it. Eric, really poured his soul into it all, for which he deserves firm handshakes all the way to the ti-pity-top of Mount Success.

Rant Radio

I have randomly stumbled upon an ancient episode of NewsReal by Sean Kennedy within my archives; as I couldn’t really find it anywhere anymore online, I have decided to make it available for your very own personal enjoyment of course.

Based on the ID3v1 metadata within the MP3, it seems to be episode 270 from 03/04/2006.

What makes this even more interesting or weird is that I had picked The Future by Leonard Cohen as the theme song of my 2025 final end of year post. What an unexpected coincidence, right?

Anyway, this little serendipitous find reminded me of the fact that I have built a super tiny “Web Radio Player” way back in late 2006, for one my cyber-friends at the time, which so it happened to have included RantRadio.com as one of the default radio stations.

Managed to track it down, it was tucked away reasonably well within the depths of my archives; it’s just a plain old Win32 application written in C, using FMOD for the audio streaming bits; which means that it still works just fine today as you can see for yourself in the screenshot below.

hak5

Now, why on earth did I make it all Hak5 themed or branded? Do you even remember Hak5, like at all?

I honestly have zero ideas about what must have been going through my mind at that particular time. Also, the fmod.dll that I have used, it seems to be from way back in 2003, which is absolutely bananas and not to mention super funny. No support for HTTPS of course! It goes to show that we didn’t really care too much about that until well into the early 2010s.

What about the rather mysterious and sus’ looking “Change Yahoo Messenger Status” checkbox? I knew right away that many of you cool nerds and nerdettes would be dying to know more about that. Therefore, I have decided to demystify it all, and take you behind the great magic curtain.

#define YAHOO_MESSENGER_UPDATE_STATUS 0x188

//
// Get HKEY_CURRENT_USER\Software\Yahoo\Pager\Yahoo! User ID
// Set HKEY_CURRENT_USER\Software\Yahoo\Pager\profiles\%s\Custom Msgs\5
//

// Find the Yahoo Messenger window by its class name
HWND hwnd = FindWindowA("YahooBuddyMain", NULL);

// Send the YAHOO_MESSENGER_UPDATE_STATUS command to said window
SendMessage(hwnd, WM_COMMAND, YAHOO_MESSENGER_UPDATE_STATUS, 0);

What is the magic number 0x188 for the SendMessage, and how did I figure that one out? The truth is that I didn’t, but I do remember finding it on some ancient long defunct forum.

The cherry on the cake however is that Eye of Sauron system tray icon. Why? Why? Why? At any rate, it’s quite interesting isn’t it, how all of this is pretty much obsolete arcane knowledge today.

Making Catacomb 3-D

John Romero should need no introductions, I hope. There will be people out there no doubt who will say that isn’t he tired of milking the good old days already?

They would be well within their rights to say that of course, however he happens to be one of the few people out there from that particular era, who have every right to continue to do so for years to come!

Besides, it’s not like there’s a plethora of exhaustive material on titles like Catacomb 3-D out there.

Bounce Tracking Mitigation

Oh, the Americans and their eternal fascination with acronyms, eh? BTM is a truly terrible thing, and it seems to have sprung forth form the same spring of madness that have blessed the world with Pulse Audio, Systemd and Wayland.

I could understand Google pushing this for their own services. You want to log me out of Google Account every now and then for no good reason? Fine, it’s annoying, but you log back in once and you get access to all the services. However, flushing cookies and local storage for all recently visited websites is just plain rude. What is the point of persistent sessions then, if I have to randomly login back into 10+ places with all the 2FA included?

You know what is even worse? There’s no way to turn it the frak off. But as always, it’s not all lost as the good folks at Brave have been hard at cooking up some fixes in order to try and address this absolute travesty of a situation. How on earth can you release something as disruptive as this to hundreds of millions of users, without giving them the ability to turn it off for good?

herring

I fully realize that I might have yapped about this before, but “the situation is mighty queer, my dear” as my great-grandmother would have put it. I also have a feeling that one of my good old friends, would have had a sudden urge to throw a dinner party had this happened to him one too many times.

eXoWin9x

exowin9x

eXoWin9x is even bigger and better than eXoDOS in every sense of the word.

You can check out the absolutely beautiful manual over here.

Ahoy: When Video Games were Brown

Only the mighty Ahoy can manage to dispel the “brown video game” myth, once and for all in a single video on the subject. The production values are through the roof as per usual.

I just realized that if you took a shot every time I said banger in this post you’d be pretty tipsy by now.

Monthly “Layoffs Report

yogibear

Two months in, and according to Layoffs.fyi about 34_650 tech folks have been laid off. Let that sink in for a moment. As these numbers will continue to grow to new heights, as per usual people will naturally try to pin all this this on some convenient boogey-man that actually has nothing to do with anything at all.

I don’t like to make predictions, especially ones that are about the future, I am very much like Yogi Bear in this regard, but considering that we had 124_201 people laid off in 2025, I think it’s fair to say that we’ll have at least double that number by years end.

As depressing as it is to keep yapping about this extremity touchy subject matter for most, I do intend to include this section in every monthly retrospective that I write this year at least.

Before anybody says anything, the answer is a resounding and clear NO. AI or LLMs have absolutely nothing to do with all this whatsoever. Just stop it, and go cry about something else in your safe corner.

Monthly “Amazon Book Review

pcintern

It’s pretty rare for me to stumble upon a review from February 12, 1999 by accident. The book in question is the rather excellent PC Intern by none other than authors Michael Tischer and Bruno Jennrich.

pc_intern_amazon_review_feb_99

Tried to dig around in order to see what they might have been up to as of late, but couldn’t really find anything tangible at all (hence why they aren’t linked); which is unfortunate of course, but such is the life of those who dare to dream in digital.

Going to have to get a hard-copy of this one sooner than later. It’s just one of those absolute must-haves.

Monthly “Coup de cœur”

This month, I ended up picking a very impressive 64k intro instead of a demo, rather aptly called Heaven Seven by Exceed, which happened to have won the first place at the great Mekka & Symposium 2000.

Please enjoy the show, and don’t forget to try the fish. Until next month, as server said: “end of line”!

https://mihail.co/2026/02/28/2026-february-retrospective/
2026 Birthday Retrospective
Show full content
2026 Birthday Retrospective

I decided to start another tradition this year in the form of so called birthday retrospective. Not entirely sure if this is something that is going to stick, nor what I am actually going to yap about exactly, but it seems like a reasonable idea to do at this time.

0x14 years since “…”

It’s pretty crazy to think about the fact that it has been 0x14 since graduating high-school. What is even crazier that if I’d been a very irresponsible young-adult, and fathered a child, that child now would be 0x14 years old. How crazy is that?

Kept in touch only with a handful of my ex-classmates over the years, which is not too unconmmon, I should think.

What about the reunions over the years? I am not much of a reunions guy, but I am sure that you’ve already summized as much if you happen to read this blog with any regularity at all.

0x1D years of “…”

0x1D years of coding this year. I started at around age 11 or thereabouts, it’s always difficult to pin this down in retrospect. I do realize that there are people out there who started at age 6 and some even earlier than that.

Now, in my particular case it wasn’t anything glorious or out of the ordinary, but I did manage to cobble together a small quiz game in the form of a [batch file][bat].

How did it work? It used the limited primitives available echo, choice, if, goto. I was unaware of course of the more advanced capabitilies that were available in the more modern .cmd variant of the language, that was available in the NT family.

It still felt like black-magic, and even after this many years I am more than happy to report that the same feeling and joy never went away. Which, I am more than sure that some might find a tad surprising to hear.

You won’t always carry a “…”

“You won’t always carry a calculator in your pocket.” is a phrase that all us millenials have heard being repeated to us ad nauseam, even though everybody understood by then that it has already lost most of it’s potency and meaning.

Truth be told, I got my very own feature phone quite late in the game, in the form of a glorious Sony Ericsson K300, which of course was way more than just a calculator in my pocket.

sek300

Thinking about all this made me realize how we all used to explore every nook and cranny of these phones back in the day. And now, we carry phones in our pockets that put absolute monsters like the Pentium 4 to shame in every respect.

Quite an interesting conondrum to ponder, isn’t it so?

Blogging

Speaking of firsts, I had my first homepage up and running on Geocities in 2002 or so. The idea of free ad-less hosting was just a dream back in those days, and one frequently ended up hopping between hosts depending on what was being offered.

At any rate, I did have some articles and tutorials up there, but it most definitely wasn’t an actual blog. So much so that in fact I can’t recall anybody even mentioning the term blog until much later into the decade.

I went through what I like to call the right of passage of the great primordial blog-o-verse: Blogger, Tumblr, Wordpress.com (both cloud and self-hosted), before scraping all the dynamic stuff, and going to back to the basics of having a statically generated site.

Truth be told, the sharp lines between the blog and the personal homepage kind of blurred together anyway by the 2010s or thereabouts.

Social Media

I always considered the early days of blogging as proto-social-media. Especially when the so called micro-blogging phenomena took off, and people started pushing out short controlled bursts of their most random going through their heads through the sideways-and-byways of the great information super-highway.

Now, just so it happens that I found myself working on a very interesting service that intersected at a pretty fundamental level with what I like to call the dawn of modern social media. If you ask someone today to name a couple of social media companies from let’s say between 2003 to 2009, they will very likely say MySpace and Facebook.

I say that, what about Bebo, Friendster, Hi5 or VK? Do any of these ring a bell?

The service eventually got sold for a considerable chunk of change way back in 2008, and the rest is history, but you young folks should remember that your elders also used to have rizz and did copious amounts of aura farming back in those early days. Even though our bodies might not have been tea, even back then. Too much non-diet Coke, I’m afraid.

Digital Archeology

I made no secret of being kind of a digital hoarder of sorts. Alright, it’s nothing to be worried about, I’m nowhere near where I have to stand in a circle stating: “I am X, and I am a digital data hoarder!”. Okay?

Anyway, the idea of having my very own curated archive composed of a mixture of old web, magazine demo CDs/DVDs, and old shareware has been swirling in my head for the past 2-3 years.

Did it reach the point, where I am finally going to do something about it, and birth it into existence? I honestly don’t know just yet, so I am going to exercise my 5th amendment right and remain silent.

Joking aside, when it happens it will probably just live under a sub-section of this very blog, as I have really no intention in maintaining yet another digital presence.

Obviously, I wouldn’t host any of the ISOs myself, and I’d offload them to someplace like archive.org.

English

I am sure that most people have probably picked up on the fact that English is not mother tongue, nor it is my first foreign language. English was introduced in the 6th grade In the middle-school that I was attending, while French was introduced in the 2nd grade of primary school.

Both of these languages have followed me all the way to the 12th grade, or the end of high-school if you want to be more precise.

This must surely mean that my French must be pretty good, right? Well, not exactly. I always said that the Gauls did end up defeating the Romans in the end, simply by drifting the French language as far away from Latin as it was humanly possible.

At any rate, I can read and understand French of course, but conversionally speaking, I’d rate my myself as having basic or limited proeficiency. All that is to say that I find it difficult to find my words. I almost forgot, I also happened to have taken 2 years of Latin, so I know full well what I am talking about here.

Now, English on the other hand. It’s a totally different story. I consider it to be the ultimate utilitarian language, and it most definitely became the New Latin of our days.

I am generally not the person who likes to prescibe to others what they must focus on to understand about the world in general, but not undertsnaing English in the year 2026 is equivalent of living in some sort of a weird pseudo-intelectual dark ages. Why, do I say that?

Think about it for a second. If one doesn’t understand English, then they are at the mercy of others to translate, and recurgitate pretty much everything of note that happens in the world. They are incapable of making up their own minds or learning about anything in real time, which puts them in a very vulnerable, and rather dangerous position, especially in today’s rapid changing world.

I am sure that people will try and employ all sorts of intelectual gymnastics to justify why all this is just fine, and dandy. Let me just say this, do not let these people fool you as they do not have your best interest in mind.

Writing

One of the ways of improving one’s ability at a certain language is by reading, writing and speaking said language with a certain regularity. I’d probably add to that list one additional item, which is thinking in said language, rather than in one’s mother tongue, can also prove to be extremely beneficial when it comes to commanding it well.

I am doing all these myself, but like I mentioned before, I’d really like to write a whole lot more, and not just short-form blog post type content, but also long form. Both on technical and non-technical subjects. Finding your own style, and your own voice can most definitely take some practice in my experience.

As an ardent fan of collections and anthologies, I’d love nothing more than to have a crack at some alternate historical fiction in the form of a series of short stories. That was a mouhtful, wasn’t it? Well, I feel like that it would be a good first foray into non-technical writing for a layman like myself.

Art

I am doing some new year’s clean-up as I migrate things to my new machine, and I happaned to bump into some paintings and drawings from middle-school. As this post wasn’t way too long already, I have decided to share them here for your pleasure or horror. Angels to some, demons to others as they say.

1

2

3

4

5

6

7

They are not much, but they are honest work of young your’s truly. I never really had any meaningful talent, but I do consider myself to have at least some minimal inherent sense of aesthetics.

The Future

All 3 loyal readers of this blog might have noticed how I became a bit more cautious when it comes to announcing my side projects. This is largely because, I came to the realization that most people do not seem to understand the basic facts that side-projects are just that; they are not part of a second job, nor do they have hard deadlines or any specific commitments.

Therefore questions like “When is it done?”, have no real meaning in relation to side-projects. The answer to that question always is and will be a very simple: “It’s done, when it’s done!”.

I’d love nothing more than to be able to retire early and spend all my available time hacking away at interesting, and perhaps not so interesting side-projects. Alas, that is not possible at the present time, and besides, I have to put food on my table which happens to require a considerable amount of my time. Given that there’s only so many hours in a day, there’s less time to spend on side-projects.

Now, I do not want to end this post with the feeling of impending doom-and-gloom as they say, so let me just put it out there that I in fact have a rather potent mixture of awesome things cooking in my owen at the moment. I do realize that I said this before, but patience is the elementary ingredient of all things as the say around these parts.

So be of good cheer, sit tight and wait! I have such sights to show you!

https://mihail.co/2026/02/11/2026-birthday-retrospective/
2026 January Retrospective
Show full content
2026 January Retrospective

The year started as good as Return to SIlent Hill ended up doing at the box office.

It’s not all doom and gloom though, I stumbled upon this funny little nugget on mighty Wokopedia, that made me smile. That must count for something, right?

The film had its release on January 23, 2026 in the United States had grossed $2 million in the United States on opening day. It is projected to gross below Avatar: Fire and Ash and another fellow newcomer Mercy, with most theaters closing down during the weekend due to the January 2026 North American winter storm.

I might return with a little blurb after I had the chance to watch it; not going to be paying too much attention to ratings or the critics for that matter; and neither should you, if you ask my honest opinion.

Oh, by the way, I forgot to tell you, that I’ve already failed one of my promises for 2026 as I didn’t end up posting at least once every week of the month. This is precisely the reason why I never really did proper New Year’s resolutions of any description.

Layoffs

amazon

In other news, we have yet another batch of 16_000 jobs, cut by Amazon. I said this last year, it’s going to be a bloodbath out there for many many years to come. It’s probably fair to claim that millenials will never know any sort of tangible job security within their lifetimes (whatever that means); the effects of which are probably going to be absolutely devastating in the long run.

One thing is certain though, the mirage of the parallel economy based purely on graphs, numbers, and excelsheets is starting to show some rather serious cracks yet again, which is never a good sign.

Hocus Focus

hf

I consider the people behind Stuck In Attic, to be dear good friends of mine, therefore I would have been remiss, if I didn’t mention their new cozy game that they just happened to announce at the beginning of January; the very aptly named Hocus Focus.

I am sure that they wouldn’t mind if it ended up selling 500_000 units or more, considering that there seems to be a fairly vibrant market for cozy games out there at the moment.

The SEO needs some more time to settle down, because right now, when one does search for the name, there’s a high chance that one might stumble upon things like essential oils instead. The ways of the great information super-highway are very mysterious indeed.

Reloaded 2: Retro Gaming Zine

rld_zine_2

Reloaded 2 came out in the middle of December last year, but somehow it managed to fall through the cracks, and didn’t make it into my last retrospective.

I really wanted to mention it, considering that it focuses on the best RTS games of 1997. Said, this before, and I’ll say it again, I am really super glad that people are still releasing these zines. They are truly a labor of love in every sense of the word.

NVIDIA

I was almost ready to forgive Jensen for the absolute disgrace that went down in the history books as the Geforce4 MX 440. In other words, selling a slightly beefed up GF2 as an entry level GF4 was an dick marketing move, no matter how you slice or dice it.

Only to wake up in the year 2026, and find out that decade old NVIDIA GPUs have been deprecated, and now are pretty much unsupported, unless one wants to spend their precious life patching the kernel module for each new kernel version out there.

As someone who has been there, and most importantly have done that, let me tell you that it’s not a journey you want to embark on; which in turn means that if you happen to have one of those affected GPUs, then you will be pretty much out of luck, and forced to use abominations like nouveau.

I can fully understand that supporting something as ancient as let’s say the NVIDIA Ion is totally out of the question, but something that is barely a decade old or thereabouts, well that’s a completely different story in my honest opinion.

Speaking of the NVIDIA Ion, here’s a patch that I cobbled together way back in late 2020, in order to make the 340.108 kernel module compile successfully on the 4.8 kernel.

--- kernel/uvm/nvidia_uvm_lite.c	2019-12-12 00:04:24.000000000 +0200
+++ kernel/uvm/nvidia_uvm_lite.c	2020-11-19 02:15:59.338918014 +0200
@@ -820,7 +820,7 @@
 }

 #if defined(NV_VM_OPERATIONS_STRUCT_HAS_FAULT)
-int _fault(struct vm_area_struct *vma, struct vm_fault *vmf)
+vm_fault_t _fault(struct vm_fault *vmf)
 {
 #if defined(NV_VM_FAULT_HAS_ADDRESS)
     unsigned long vaddr = vmf->address;
@@ -828,13 +828,14 @@
     unsigned long vaddr = (unsigned long)vmf->virtual_address;
 #endif
     struct page *page = NULL;
+    struct vm_area_struct *vma = vmf->vma;
     int retval;

     retval = _fault_common(vma, vaddr, &page, vmf->flags);

     vmf->page = page;

-    return retval;
+    return (__force vm_fault_t) retval;
 }

 #else
@@ -868,7 +869,7 @@
 // it's dealing with anonymous mapping (see handle_pte_fault).
 //
 #if defined(NV_VM_OPERATIONS_STRUCT_HAS_FAULT)
-int _sigbus_fault(struct vm_area_struct *vma, struct vm_fault *vmf)
+vm_fault_t _sigbus_fault(struct vm_fault *vmf)
 {
     vmf->page = NULL;
     return VM_FAULT_SIGBUS;

Anyway, it’s no point in crying over spilled milk, but what a shame nonetheless.

If you had the impression that this rant was going to be about Jensen not caring about the household variety gamers anymore, then I am sure that you’ve been mighty disappointed. Sorry, not sorry!

Khajiit has new wares, if you have coin!

The NVIDIA kernel module situation has forced me set condition one through the ship, and launch the alert vipers. I really didn’t want to reach into the war-chest (reserves), given the current economic, and general situation in the world, but alas it was necessary.

I always buy ASUS, as they seem to be only general purpose hardware company out making products that just last, without breaking down soon after their warranty runs out.

tuf

The upgrade is better illustrated with numbers than words, so let me lay it all out in a table.

Before After 16GB RAM 32GB RAM 4 cores + 8 threads 8 cores + 12 threads 4GB dedicated NVIDIA GPU integrated Intel iGPU + 6GB dedicated NVIDIA GPU 250GB SSD + 1TB HDD 512GB SSD

The only thing that is kind of a downgrade is the 512 SSD, but I’ll very likely get an external drive via USB-C to satiate my needs for judicious data hoarding.

I also intend to use this as an opportunity to clean-up, and perhaps also consolidate some of my ancient .dotfiles, which happens to be a thing that I really wanted to do for quite some time, but I always managed to find some reasonable excuse to put it off.

2026 is going to be the year of no excuses or half measures. You’ve heard it here first folks!

Enshitification (Contd.)

If the nitroglycerin, sorry I meant to say the glycin wasn’t bad enough in terms of absolutely bonkers regressions across the board, most of which haven’t been addressed, and given the situation with GNOME and GTK very likely never be; then you’ll be glad to find out that the global enshitification continues in full force.

This time around in vim. It will be three years this August since Bram has unexpectedly passed away, and just like I predicted, breaking changes are being introduced left and right, without anyone batting an eye.

How on earth, can the number of arguments be changed on a function that is part of the public API, and is known to be relied on by countless plugins out there?

" netrw#BrowseX: (implements "x") executes a special "viewer" script or
" program for the given filename; typically this means given their
" extension. 0=local, 1=remote
-function netrw#BrowseX(fname,remote)
+function netrw#BrowseX(fname)

How difficult can be it for these people to understand that this is something you just don’t do? Don’t answer that, as this was a purely rhetorical question. Not really, but here we are!

The only explanation that I can come up with to explain this attitude is that people seem to be under the impression that coding is like cooking. In other words, something half-edible will come out in the end, no matter what you do. I am not so sure that I agree, but it’s definitely a certain point of view.

salmon

So, instead of reverting this change, what ended up happening instead is that all affected plugins came up with specific patches like this one by Tim Pope.

Never ever do this kids. Don’t make a grown man cry. Pretty, please?

Monthly “Coup de cœur”

Instead of the monthly “Dad Joke”, I thought that I’d switch things up a little this year around, and present a monthly “Coup de cœur” from the demo scene instead.

This month, I picked one of my all time favorites from way back in 2000, called VIP2 by Popsy Team.

Please enjoy the show, and don’t forget to try the fish. Until next month, end of line!

https://mihail.co/2026/01/31/2026-january-retrospective/
2025 Retrospective
Show full content
2025 Retrospective

Let me kick this off with the obligatory GitHub contributions chart.

2025_alt

2025

Why are there two charts? Because, I’ve been using two separate accounts. This is one of the weakest aspects of GitHub. One should really have a single account, and then one or more profiles, similar to how one account can house multiple channels on YouTube. Oh well!

Archive

Here is an archive of all the posts that I published this year.

2025-01-31 / 2025 January Retrospective 2025-02-28 / 2025 February Retrospective 2025-03-08 / FreeBSD 2025-03-31 / 2025 March Retrospective 2025-04-30 / 2025 April Retrospective 2025-05-31 / 2025 May Retrospective 2025-06-17 / Lomo: Smena Symbol 2025-06-30 / 2025 June Retrospective 2025-07-31 / 2025 July Retrospective 2025-08-17 / The curious case of Theseus and his long-ship 2025-08-31 / 2025 August Retrospective 2025-09-30 / 2025 September Retrospective 2025-10-07 / La luna del cacciatore 2025-10-31 / 2025 October Retrospective 2025-11-23 / Monkey-patching WEBrick 2025-11-30 / 2025 November Retrospective 2025-12-31 / 2025 Retrospective

I might attempt to do something crazy, and try to publish at least one post per week in 2026. It does sound a bit aggressive, I have to admit, but there’s only one way to find out.

Stronghold Crusader: DE

In last year’s retrospective, I was talking about how Stronghold Cursader: DE was still nothing more than a good old fashioned conspiracy theory, but it turned out to be true in the end; just like all conspiracy theories in this day and age.

You’ll be pleased to know, that this is the last time you’ll hear me mention it this year. Cough, cough.

2001

Playing an era appropriate instrumental version of a hit song in the background, for each segment was a very nice touch in my humble opinion. Can you name them all? Without resorting to any of the so called song recognition tools out there of course!

Lua 5.5.0

Lua 5.5.0 got released on December 22nd. No significant changes have been introduced since the beta that I checked out a few months back; a fact which really shouldn’t be too surprising to anybody, but I still thought that I’d mention the release, considering that it has been a while since 5.4.0.

Advent of Slop

The people have spoken, and seem to have designated “slop” as the word of the year. This is primarily due to so called AI slop, of course; but I’ve seen it applied to all sort of things over the course of the year, so it’s fair to say that it has not been exclusively limited to it.

Personally, I do not feel like there has been any increase in random crap-ware and creepy-pasta being spread through the great information super-highway. Sure, AI, it definitely makes things easier, but people were already creating “meme generators” and such, a long way before the current so called cycle of AI reared its head.

My personal word of the year on the other hand is learing. A little bit of a late call, but what can we do?

learing

The Future

On a slightly more serious note, 2025 was an absolute cluster-fuck of a year in every imaginable way, and I am afraid to break it to you, 2026 is going to be even more brutal.

People will always look around and try to find a scape-goat or a recipe that has been prepared just for them, so that they do not have to do any thinking on their own. The reason why everything is deteriorating at a rapid pace, has nothing to do with video games, guns, AI, climate change or the fact that little Andy watched one too many shorts on TikTok or some place.

What is it then? It’s pure socialist brainrot that has been working its way all our lives, but only now it’s starting to reach critical mass, and it taints everything it touches.

As someone who happened to be born in the 80s in a country that has been ruled by a third-rate brainless illiterate shoemaker, and his retarded illiterate (read: savant) wife, believe you me, when I tell you that if you think that things are bad right now, then you haven’t a faintest clue as how much worse they can get.

All that is to say, I feel like 2026 will force my hand to step on the gas, and make some major changes, because retiring early, no longer seems feasible at this point, mostly due to the rate at which I am able to fill up my war-chest at is simply not being meaningful enough anymore.

I mentioned before the fact how I never jumped on any of the proverbial gold-rush trains in the past (and there have been a couple!), mostly because, I never seen any meaningful reasons to do so, but I feel like it’s high time to buckle up, and buy some shovels in 2026.

At any rate, until next year, as server said: END OF LINE!

https://mihail.co/2025/12/31/2025-restrospective/
2025 November Retrospective
Show full content
2025 November Retrospective

I find myself writing this up at the end of the month once more, while getting ready for my usual month long sabbatical. The weeks turned into months, very fast this year, perhaps faster than usual as cliché as might sound. Luckily for you, I decided to leave my more juicy musings about everything that has transpired during the course of this year, for my last retrospective. Very surprising, I know!

Stranger Things

I am more than sure, that you might have guessed the fact that I’m going to have a word or two say about the last season of Stranger Things franchise.

Naturally, I binged the first four episodes in one sitting, as it is customary for any hardened specimen of the homo-netflixoidus hominid sub-species. Is it any good? Well, I am happy to report that it is still very much Stranger Things, for what it’s worth.

Be that as it may, releasing a show in this format, with half of the season coming out in the month of December no less, and then not making it christmas-holiday-themed is a major a blunder in my opinion.

Another thing that has been swirling around in my head for quite some time now, is the fact that Kingdom: Eighties, would have been an absolutely fantastic tie-in for the whole franchise. It got all the ingredients, and replacing the greed with some creatures from the upside down, would have been such a natural fit.

Such a missed opportunity. Oh well!

Black Phone

I really don’t know, how I managed to totally miss the original Black Phone when it initially came out.

Think of it as Freddy Krueger, meeting up with Candyman, and Jason Voorhees among others at a house-party organized by none other than Bughuul. If all that didn’t raise your interest one bit, then I don’t know what else I could possibly say that would.

Stronghold Crusader: DE

I am always amused by how people still don’t seem to get how important it is to make sure that a game is mod-able on release.

Just look at the amount of content the community has conjured up in just a few months after release. There is some sort of a deep lesson in there somewhere, for the people who always seem to cry out in the end: “Mr. President, give us back our 30%! Now!”.

Epic & Unity partnership

If you were sorta-kinda running low on Unity drama, then the Epic & Unity partnership announcement should have replenished your supply to last just a little while longer.

I know that people must be tired of me repeating this ad nauseam at this point, but seriously folks, just roll your own tech. It’s the year 2025 for crying out loud. You don’t need Unity, even if you think you do.

s&box

In a rather unexpected turn of events, s&box became open source this month, and is now available under the very permissive MIT license. This release doesn’t include Source 2 of course, which is the actual engine that powers it all under the hood.

One particular thing that I was curious about, when it came to s&box for the longest time, was the way they implemented their own custom window decorations. I knew that they were using Qt, but I wasn’t sure if they simply went it by using the available controls (widgets), or if they have done it like we used to do it way back in the good old days, by the means of so called owner drawn controls.

sbox

It didn’t take an awfully long time after peeking under the hood in order to discover the absolute truth inside a very aptly named source file called engine/Sandbox.Tools/Qt/Window/TitleBar.cs.

using System;

namespace Editor;

internal class TitleBar : Widget
{
    private Widget Window { get; init; }
    private Button IconWidget { get; set; }
    private Label TitleLabel { get; set; }
    private Widget Grabber { get; set; }
    private WindowControlButton MinimizeButton { get; set; }
    private WindowControlButton MaximizeButton { get; set; }
    private WindowControlButton CloseButton { get; set; }
    public TitleBarButtons TitleBarButtons { get; init; }

    public MenuBar MenuBar { get; private init; }

    private const int IconSize = 18; 

    public Pixmap IconPixmap
    {   
        set 
        {   
            IconWidget.SetIcon( value.Resize( IconSize ) );
            Update();
        }   
    }
    
    // ...
}

These widgets are then explicitly passed back to the so called native frameless main window in C++ land.

internal void SetTitleBarWidgets(Native.CFramelessMainWindow nativeWindow)
{   
	nativeWindow.SetTitleBarWidgets(
		IconWidget._button,
		TitleLabel._label,
		MenuBar._menubar,
		Grabber._widget,
		MinimizeButton._widget,
		MaximizeButton._widget,
		CloseButton._widget
	);  
} 

The grabber is quite an interesting choice of name for a member variable, but this is a safe place where we don’t judge anything or anyone for that matter.

Decima Engine

While we are on the subject of custom title bars, I would be remiss, if I didn’t mention the Decima Engine, which seems to be doing something similar, as you can see for yourself in the screenshot below.

decimaeditor

The only difference here is that it puts the tab bar, rather than the menu bar inline inside the title bar. This makes sense to me, if the menu bar is very context specific, and each tab is almost like a self-contained tool or sub-tool in of itself.

Those who are so inclined, and are interested in the more nitty-gritty details can find a fairly detailed presentation about how the actual user interface is being rendered, and so on.

The G* you leave behind

Another upgrade, yet another awful regression in something that starts with the letter G. What is it, this time around? It’s gdk-pixbuf, thank you very much for asking. It’s so kind, and considerate of you to check in at such a difficult time.

I really need to start the process of de-g-ification soonish, and simply get rid or replace every piece of software that relies on anything that is somehow related to GTK and GNOME.

This is an absolute disgrace, how on earth can you introduce a major regression, and then subsequently refuse to address it for several minor and patch versions? The patients are truly running the asylum now, there’s no going back from here.

Winter OTK Games Expo Monthly Dad Joke

Q: Why don’t skeletons ever say “May I meet you?”.

A: Because, they are dead-icated to being alone.

https://mihail.co/2025/11/30/2025-november-retrospective/
Monkey-patching WEBrick
Show full content
Monkey-patching WEBrick

In the early days of GitHub Pages, I found myself hosting this blog on a Heroku dyno, backed by the rather generous free-tier that they used have on there. Gone are those days, am I right?

Naturally, I ended up rolling my own static site generator in the process, which I have written in Python. This was a rather uncertain time in the Python ecosystem, because it happened to coincide with the initial introduction, and subsequent release of Python 3.

Anyway, at some point in time, when the enshitification of Heroku has started to rear its ugly head, I have already been a paying GitHub user for quite some time, which made me realize, that perhaps, I should just jump ship, and migrate the whole shebang to GitHub Pages. A rather sensible thing to do, correct?

So, I did, but still kept using my own static site generator instead of Jekyll, which I did until about the annus horibilis, that happened to be the year of 2020.

The transition wasn’t really anything to write home about, but I really liked the idea of not having to regenerate everything locally, then push it up in the repository, because now Jekyll would take care of that automatically for me.

WEBrick

And, now we have arrived at the real subject matter of this post. Jekyll happens to be using WEBrick to provide a tiny HTTP server, which is then used to serve the statically generated site on localhost. This works great, until one tries to embed a <video> or <audio>, both of which happen to require support for so called HTTP partial content, which happens to be more colloquially known as range requests.

Support for which has been implemented in a really half-assed manner in WEBrick, as it turns out. I always considered this to be a relatively minor annoyance, and said to myself several times over the years, that it might be a good idea to just wait it out. Surely, someone else will come around, and fix it in due course.

I’d check on the ruby/webrick repository on GitHub every now and then. Sadly, no dice. Until relatively recently, when this particular pull request popped up seemingly out of nowhere.

Alas, this is where the good news ends, as these changes don’t really fix the issue in question, they seem to make it worse. So, I said to myself, enough is enough.

Unlike the author of the pull request, I really had no desire in battling through the politics that would be necessary in order to get a proverbial fix merged in, which left me with the only remaining viable option of cooking up, and maintaining my very own monkey-patch. Not my first rodeo, you know?

Monkey-patch

The first order of business was to get the monkey-patch loaded before Jekyll ends up booting. Unfortunately, the generated binstub doesn’t do a Bundler.require!, which meant that I couldn’t get away with simply doing something along the lines of:

gem "jekyll-webrick-monkey-patch",
	path: "gems/jekyll-webrick-monkey-patch",
	require: true

No matter, the following did the trick just fine instead:

require "rubygems"
require "bundler/setup"

require_relative "../lib/jekyll-webrick-monkey-patch"

load Gem.bin_path("jekyll", "jekyll")

With that in place, it was time for me to get started by taking a look at WEBrick itself, in order to figure out where and what to actually monkey-patch.

$ cd $(bundle show webrick)
$ ls
Gemfile  lib  LICENSE.txt  Rakefile  README.md  sig  webrick.gemspec

It didn’t take awfully long before I landed in lib/webrick/httpservlet/filehandler.rb, which is exactly the spot where the magic seems to happen.

When investigating something like this, I like to employ a combination of so called printf, and runtime debugging by using something like pry.

So, I started out with something relatively basic:

# ...

def initialize(server, local_path)
	super(server, local_path)
    @local_path = local_path
end 

# :stopdoc:

def do_GET(req, res)
    if File.extname(@local_path) == '.mp4'
		puts req 
		debugger                             
	end

    st = File::stat(@local_path)
    mtime = st.mtime
    res['etag'] = sprintf("%x-%x-%x", st.ino, st.size, st.mtime.to_i)

# ...

Running Jekyll with these changes, led to the following output:

GET /media/2025/avp.mp4 HTTP/1.1
Connection: keep-alive
Accept-Encoding: identity;q=1, *;q=0
Accept: */*
Accept-Language: en-US,en;q=0.9
Range: bytes=0-
   45: def do_GET(req, res)
   46:   if File.extname(@local_path) == '.mp4'
   47:     puts req
=> 48:     debugger
   49:   end
   50: 
   51:   st = File::stat(@local_path)
   52:   mtime = st.mtime
   53:   res['etag'] = sprintf("%x-%x-%x", st.ino, st.size, st.mtime.to_i)

Two things immediatelly popped out, first the Range header, which I expected, and then the fact that the computed etag header in the response, doesn’t take the Range header into account, which in turn means that the next range request will end up raising a HTTPStatus::NotModified.

And, now the moment you’ve been all waiting for. I simply ended up disabling caching by returning false for all range requests, and voilà, problem solved! No fuss, no muss!

# frozen_string_literal: true

require "webrick"

module JekyllWebrickMonkeyPatch
  def not_modified?(req, res, mtime, etag)
    if req["Range"]
      false
    else
      super
    end 
  end 
end

WEBrick::HTTPServlet::DefaultFileHandler.prepend(JekyllWebrickMonkeyPatch)

Now, like I said in the beginning, I had no intentions in fixing this by trying to implement the actual specifications in order to make range requests play nice with caching. I just wanted to be able to play the media. So, if you were absolutely ready to dust off your pitchforks, please don’t, it simply doesn’t worth your trouble. Trust me, I should know.

One additional thing I always like to add when doing something like this; is what I like to call a proverbial version-check-time-bomb (a mouthful, I know), which will blow up, if or when the library I happened to have touched has been upgraded.

if Gem::Version.new(WEBrick::VERSION) > Gem::Version.new("1.9.1")
  raise "Please check if this monkey-patch is still necessary!"
else
  puts "Applying WEBRick monkey-patch for range requests ..."
  WEBrick::HTTPServlet::DefaultFileHandler.prepend(JekyllWebrickMonkeyPatch)
end

This is a very convenient way of avoiding rather nasty surprises down the line, and growing less gray hair before the age of 45, should always be considered nothing more than an added bonus.

https://mihail.co/2025/11/23/monkey-patching-webrick/
2025 October Retrospective
Show full content
2025 October Retrospective

Octobers seem to alternate between elections and massive layoffs. Over 112,732 employees have been laid off by 218 companies so far in 2025, according to Layoffs.fyi. This number doesn’t seem to include the recent 30,000 yet, as this was announced only a couple of days ago by Amazon.

People are coming up with all sorts of outlandish theories around these layoffs, but they all fail to realize the simple fact that the recension hasn’t even begun in earnest just yet. And, when it finally does, they are going to be in for a surprise. I have said this a couple of times before, but what is coming is going to make 2008-2009 look like just a bad fever dream.

Katee “Starbuck” Sackhoff

I totally wasn’t expecting Katee Sackhoff to make an appearance on JRE. It most definitely wasn’t on my 2025 JRE bingo card, especially since Bill Murray popped up rather unexpectedly as well, earlier in the year.

Katee will always be Starbuck to me. One of the many downsides of portaying such an iconic character so early in ones’ acting career is that it’s going to be very difficult for one to divorce themselves from said character later on. Which can be a blessing, and a curse at the same time, depending on how one looks at and deals with it.

For the younger generations out there, she’s probably more Amunet Black or Bo-Katan.

Battlestar Galactica

If I was given the task of summing up Battlestar Galactica in only three clips, these would be the ones that I’d pick without even flinching.

So say we all!

Riddick

I feel like Riddick is a pretty underrated franchise all things considered, and it’s definitely one of those, where I totally didn’t expect Katee to show up.

Toasters

speakers

While on the subject of walking chrome toasters, it appears that in our particular case, they might turn out to be walking gray speakers instead. This was my rather cringe attempt at making a reference to NEO by 1x, of course.

I need to up my shit-posting-meme-game, that’s for sure.

The “AI” browser wars

Unless you have been living under a rock, you probably have heard about ChatGPT Atlas by OpenAI.

I have no desire to comment on the fact that it’s just a Chromium fork. My qualms go deeper than that, and aren’t directly related to this product in particular.

But, this is a larger subject that I am not ready to dive into just yet. It warrants a dedicated post of its own.

Porting Marmota to GTK4

I took another stab at trying to port marmota to GTK4 this month, which was rather foolish of me to be perfectly, and totally honest.

What is the point of using GTK at all, when one has to start writing code for the underlying toolkit(s), which in this particular case happens to be X11, just to be able to center a bloody window, mind you.

static void mrt_center_window(mrt_context_t *ctx)
{
#if GTK_CHECK_VERSION(4, 0, 0)
	int w, h;
	Display *xdisplay;
	Screen *screen;
	Window xwindow;
	GdkDisplay *display;

	if(!gtk_widget_get_realized(ctx->win))
		return;

	display = gdk_display_get_default();
	if(!GDK_IS_X11_DISPLAY(display))
		return;

	#pragma GCC diagnostic push
	#pragma GCC diagnostic ignored "-Wdeprecated-declarations"
    xdisplay = GDK_DISPLAY_XDISPLAY(display);
	if(xdisplay == NULL)
		return;

	screen = ScreenOfDisplay(xdisplay, 0);
	if(screen == NULL)
		return;

    xwindow = GDK_SURFACE_XID(gtk_native_get_surface(GTK_NATIVE(ctx->win)));
	#pragma GCC diagnostic pop

    gtk_window_get_default_size(GTK_WINDOW(ctx->win), &w, &h);

    XMoveWindow(
		xdisplay,
        xwindow,
        (screen->width  - w) >> 1,
        (screen->height - h) >> 1
    );
    XFlush(xdisplay);
#else
	gtk_window_set_position(
		GTK_WINDOW(ctx->win),
        GTK_WIN_POS_CENTER_ALWAYS
    );
#endif
}

Based on the fact that GDK_SURFACE_XID has been marked as deprecated, I think that it’s fair so say that GTK is no longer a portable toolkit, but rather a toolkit that serves nothing but Wayland.

Now, as if all this wasn’t bad enough already, setting the icon of a window has also been turned into an overengineered hot mess.

Can you imagine that in the year of 2025, one has to create special definition files and go through largely undocumented hoops in order to set the icon of a window?

gtk_window_set_icon_name("terminal");

This is completely useless now, and does nothing or does the wrong thing on most configurations. Why even include this in the API at all?

While GTK3 is very likely going to be kept alive in some form or another for years to come, I personally see no reason to keep writing or using any software that relies on GTK at this point.

It remains to be seen if Qt will start making the same mistakes in the near future. For the sake of the Linux desktop, I really hope not.

I am using all my will power in order to resist the urge of writing my own terminal emulator from scratch, one that relies on nothing more than X11, FreeType and OpenGL.

Stronghold Crusader: DE - The Canary and The Trader DLC

I know, I know. I’ll just leave this here. Enough said.

Megabonk

megabonk

Megabonk ended up bonking for most of the month of October. It seems to have sold over 2 million copies, according to the estimates provided by Gamalytic.

I consistently seen over 45,000 people playing it on Steam, which is pretty bonkers to say the least, especially after more than a month since its launch.

BALL x PIT

ballxpit

BALL x PIT is a rather fresh, and interesting rogue-lite take on the classic Breakout.

Paged Out!

pagedout

I always found it very weird that with the slow death of physical magazines, digital magazines seem to have almost completely died out as well, which never made sense to me.

Especially today, when PDF is ubiqutous, and can be consumed without having to through the pain of installing the mighty Adobe Acrobat Reader. It was probably included in more installers than any other piece of software ever.

Imagine my surprise, when I randomly stumbled upon Paged Out!, which have filled me with nostalgia for the times when I used to buy magazines with my pocket money on Friday afternoons, and then devour them cover-to-cover over the weekend.

And, then the bonus wallpapers that come with each issue are absolutely gorgeous. Total chef’s kiss!

eXoDOS

exodos

The whole eXoDOS project is most definitely treading on a very thin gray line, between the so called preservation of abandonware, and privacy.

As I am not a judge or a jury, I’ll let you decide for yourself. But, I’ll say this, the manual itself screams of nothing more than an absolute passion project.

Monthly Dad Joke

Q: Why did Hercule Poirot refuse to bob for apples on Halloween?

A: Because the little grey cells insisted: “Mon ami, it is elementary, dunking one’s moustache in a bucket is simply a crime against symmetry!”

https://mihail.co/2025/10/31/2025-october-retrospective/
La luna del cacciatore
Show full content
La luna del cacciatore

If you have watched Alien vs. Predator from 2004,, then you surely will remember the now infamous Hunter’s Moon scene, which happens during an exchange between Alexa “Lex” Woods, and one of the Italian archaeologists, professor Sebastian De Rosa.

If none of this rings a bell to you, not to worry, because I did all the hard work for your pleasure and entertainment. Please enjoy the following short clip, which should classify as fair-use under the rule of the law. Or at least that’s what I like to tell myself.

What better time to dedicate a post to AVP, than on the day of the super-moon itself.

How silly of me, I almost forgot to share the trailer in all the excitement.

I do realize that many people are not too keen on the idea of a shared Alien and Predator universe, but I am not one of those people, obviously!

Take a look at what mighty Ridley Scott himself had to say about it all. I guess it’s totally fair to say that he’s not that big of a fan of it either.

One thing that was always rather bemusing to me is how some of the critics brought up the subject of cultural and historical inaccuracies. Makes one wonder, which part of science fiction don’t these folks understand? This line of reasoning is not unique to this franchise by any means of course.

At any rate, now it’s time for me call it a night, and re-watch the movie for the Nth time.

https://mihail.co/2025/10/07/la-luna-del-cacciatore/
2025 September Retrospective
Show full content
2025 September Retrospective

It’s September, that’s pretty much the start of the early-spooky-season, right? Therefore, I thought that it would be appropriate to have Conjuring: The Last Rites as the theme of this month’s retrospective.

I even adjusted the link highlight color to be redrum. As far as the movie goes, I haven’t had a chance to watch it yet, but based on the trailer it feels like a proper banger that will do proper justice to the Conjuring Universe. Or perhaps, I should just take my rose-tinted glasses off during this rather bitter-sweet moment?

It turns out that I have gotten my wish after all, and it was very mild and warm September. What more could one want in their lives?

In other news, you’ll be pleased to know that there won’t be a single mention of Stronghold Cursader: Definitive Edition this time around. Phew. You can rest easy.

s&box

One thing that people building products and games seem to get wrong more often than not is the need for frequent and meaningful communication with the customers of said products and/or games.

This is especially true for the so called indies. How many times you stumbled upon a game on Steam or a product on Kickstarter, with no updates in months or years. The excuses are always the same, to the point of becoming boring:

Oh, we are very busy!

Oh, it takes a lot of time and effort to write up an update.

Oh, there isn’t much to write about.

Sounds familiar? It does take some time to write up something meaningful, but it is not an unreasonable or unsparable amount of time by any stretch of the imagination.

Some folks will say, well that is why you need a marketing and social media department, and us poor indies cannot possibly afford that. This is all extremely meaningless cry-baby-woe-is-me type of drivel, totally inconsistent with reality itself, and utter poppy-cock to be perfectly blunt.

If marketing is your only channel of communication, then your product or game is dead, might as well just spare everybody the trouble, and close up shop. I am telling you this as someone who has spent a significant portion of their professional career building tools for marketers. In other words, I know full well, what I am talking about.

I always like to bring up Facepunch Studios as an example of how to do this right. They are the folks behind a couple of small titles (clears throat!), like Garry’s Mod, Rust and now s&box.

s&box has monthly updates, which is simply a compilation of screenshots, videos, code-snippets and other tidbits that are sometimes accompanied by small impromptu blurbs of text. Nothing too fancy, but it works quite well, and everybody knows what to expect. There are no endless threads on forums titled “Is this abandoware?”.

Why do I bring all this up? Well, while I was reading through their July Update, I noticed something that really perked my interest. I am talking about the mention of Box3D, naturally!

We then moved to Box3D, by the creator of Box2D, which will eventually be free and open source. Rubikon and Box3D are closely related, so this isn’t a major difference for users, but it’s good for our ecosystem to use libraries that are open source and continually updated.

I always hoped that the talented Erin Catto, the author of Box2D, might take pity on us mere mortals at some point or another, and bless us with a 3D physics engine that manages to keep the spirit and usability of Box2D. And, here it was, my eyes weren’t deceiving me. It has finally happened.

Speaking of Box2D, it came to me that I did a rather silly tech-demo of sorts using it, somewhere in 2008 or maybe 2009. Managed to dust off the source code, and compile it without too much fuss, which is a bit surprising, considering that it uses Qt for almost everything. I expected there to be more code-rot as it’s customary with anything written in C++, especially when involving a giga-normous behemoth of a toolkit like Qt.

-#include <QtGui/QApplication>
+#include <QApplication>

The sprites are from a particular Play with your peas prototyping-art-pack found on lostgarden.com.

Do you even remember lostgarden.com, bro? Damn, I feel old just by thinking about it.

Electron delenda est!

One might argue that Electron has become the Flash our current era, which wouldn’t be too far from the truth, but I claim that it’s actually far worse. The seed of the idea has its merits, that much is true, but that’s where the pros stop, and the long laundry list of cons start.

The idea of re-purposing people with a certain set of skills, to do other adjacent things is as old as this industry, and every single time it has been attempted, it ended in nothing but yet another catastrophic failure.

Do you remember, when everybody and their grandmother was building their in-game UI in Flash all of the sudden? The premise of that fad had its root in the same line of reasoning as Electron. Let’s have our people who are familiar with Flash, start working on in-game user interfaces. Everybody profits, right? No, not really!

Now, when it comes to Electron it seems that its maintainers really love mucking around with private and/or undocumented APIs as it can be seen in this pull request.

// By overriding this built-in method the corners of the vibrant view 
// (if set) will be smooth.
- (NSImage*)_cornerMask {
  if (self.vibrantView != nil) {
    return [self cornerMask];
  } else {
    return [super _cornerMask];
  }
}

We have a saying in this particular part of the world, that goes something like this in a more literal translation: “When you play stupid games, you tend to win stupid prizes!”. And, that’s precisely what these people did by overriding a private method.

When people get upset about Apple rejecting certain apps that use undocumented or private APIs on their iOS app store, this is the reason why.

In the case of Electron, the situation is much worse of course, because it’s a toolkit or platform if you will, which means that when things break in fundamental ways, then hundreds of thousands of apps break in unison.

Substack

I always considered Substack to be a slightly more posh or elite version of Medium. It turns out I was totally off base with my perception (first time?); it’s more like as if Tumblr and X had a weird baby.

A really strange and rather peculiar place, with a proper feed (whatever that means!), and everything included. I have to admit that I was shook for a split second. Had to pinch myself to snap out of it.

YCombinator + GitHub Phishing

I never ceases to amaze me the lengths these people go when it comes to phishing. I do wonder how many people have fallen for this particular attempt, which was rather convincing of course.

yc

It would be interesting to see how many people have fallen for it, especially in this climate, where the AI hype-cycle is still relatively strong, despite what the naysayers might want you to believe.

Campfire

It looks like one of the members of the great once.com buy-once-experiment has went full open-source. I am talking about the re-imagined Campfire of course.

Some people were brining up the fact that this isn’t fair to the people who have used their hard earned green American dollars, and bought it at over the course of last year or so.

Well, my message to those people is that long summers, rich harvests, plentiful gain; none of them lasts forever. One can keep complaining about it, or just keep walking with a smile on their face.

Complaining is much easier, I have to admit.

Conflict 3049

I am always surprised by what I manage to stumble upon when it comes to itch.io. Truly channels my inner vibe of “One man’s trash, is another man’s treasure”.

This is exactly how I found out about Conflict 3049, which seems to be a one-man passion project type of a deal from the looks of it.

A little bit rough around the edges, and even somewhat peculiar, but pretty amazing.

But, wait, there’s more. I have a very serious question for you. When was the last time you compiled a game.cs that was 32000 lines long?

$ wc game3d.cs -l
32510 game3d.cs

Yes, you read that right, it’s not a typo. The question is, will it compile on Linux?

I started by converting the windows new-lines to unix ones, by running:

$ dos2unix game3d.cs

Then, I bumped the .NET version to 9.0.

diff --git a/Raylib-cs.csproj b/Raylib-cs.csproj
index 6a034a6..d735c46 100644
--- a/Raylib-cs.csproj
+++ b/Raylib-cs.csproj
@@ -1,7 +1,7 @@
 <Project Sdk="Microsoft.NET.Sdk">
   <PropertyGroup>
        <OutputType>Exe</OutputType>
-    	<TargetFrameworks>net6.0</TargetFrameworks>
+    	<TargetFrameworks>net9.0</TargetFrameworks>
        <AllowUnsafeBlocks>true</AllowUnsafeBlocks>
        <LangVersion>preview</LangVersion>
   </PropertyGroup>

Then, it was time to build it. I must confess that I cheated a little bit, and added #pragma warning disable CS8981 to game3d.cs in order silence some of the obnoxious warnings.

$ dotnet build
Restore complete (1.1s)
  Raylib-cs net9.0 succeeded (6.3s) → bin/Debug/net9.0/Raylib-cs.dll

Build succeeded in 8.0s

Now, I just needed to grab the pre-compiled shared libraries of Raylib for Linux, and unceremoniously untar them into the bin/Debug/net9.0/ directory. Why didn’t we just call them tar-pits?

$ ls
libraylib.so   libraylib.so.550    Raylib-cs.deps.json 
Raylib-cs.pdb  libraylib.so.5.5.0  Raylib-cs
Raylib-cs.dll  Raylib-cs.runtimeconfig.json

Then, last but not least, I also copied the files/ directory, and finally launched the game with the following incantation:

$ cp media/potato.txt media/potato_windowed.txt
$ <edit media/potato_windowed.txt>
$ ./Raylib-cs potato_windowed.txt

c3049

On exit, you might notice two distinct things that went wrong, the configuration path is all messed up, and then the more obvious segmentation fault.

Local Storage Error:Could not find file '/home/user/.local/share\config3049_firebase\localstorage.txt'.
Segmentation fault         (core dumped) ./Raylib-cs potato.txt

The first one is a relatively easy fix. No fuss, no muss! Just, don’t forget to clean up your ~/.local directory afterwards.

// ...

#if false
	string folderd = Environment.GetFolderPath(
		Environment.SpecialFolder.LocalApplicationData
	) + "\\" + NETTITLE;
#else
	string folderd = System.IO.Path.Join(
		Environment.GetFolderPath(
			Environment.SpecialFolder.LocalApplicationData
        ),
    	NETTITLE
	);
#endif
    
// ...

#if false
	filename = Environment.GetFolderPath(
		Environment.SpecialFolder.LocalApplicationData
	) + "\\" + NETTITLE + "\\localstorage.txt";
#else
    filename = System.IO.Path.Join(
   		Environment.GetFolderPath(
        	Environment.SpecialFolder.LocalApplicationData
        ),
        NETTITLE,
        "localstorage.txt"
   	);    
#endif
   
// ...

As for the nasty segmentation fault itself, I really couldn’t be bothered to fix it, but it’s happening during the “unloading” (read: freeing) of resources.

// ...
UnloadHelper.UnloadAll();
Raylib.UnloadRenderTexture(ui);
Raylib.UnloadRenderTexture(screentexture);
// ...

I’ll leave that as some sort of an exercise for the reader. It’s not much, but it’s honest work.

#ChatControl

I am really surprised to see how little noise is about this, compared to all the previous times. Makes one really question things a whole lot more.

There’s a pretty big difference between issuing a subpoena to get the messages, emails, etc. of someone on suspicion of any legal mischief, and to preemptively scanning absolutely everything in real-time.

If this passes and comes into effect, it will cross a line into the territory of things that simply cannot be undone, and will usher in the beginning of a pan-european socialist nanny-state. The fact all this is even contemplated as a concept, should worry everyone, regardless of their station in life.

It warrants a national day of mourning and pity. May the Lords of Kobol have mercy on our souls.

The Tower Stream

The Primeagen should need no introductions, whatsoever, I hope. This was the second so called tower stream, where he and his rag-tag bunch of cyber-misfits took it upon themselves to vibe-code a tower defense game.

I think that the videos above should speak for themselves. Enough said.

Keychron B1 Pro Ultra-Slim

Alas, my trusty ThinkPad TrackPoint Keyboard II has finally given out, and because it didn’t look like I could source a new one from any of the local resellers, I decided to try a something rather special, and impulse bought a Keychron. My thinking was that if I doesn’t work out, then I’ll just buy another ThinkPad straight from the source, with all the import duty fees and all.

kb1pro

Now, I don’t want to hype it up too much just yet. Considering that it’s only been around 2 weeks at the time of me writing this, but I must say that it’s probably one of the best keyboards that I ever had.

The only minor gripe that I have is the size of the Up and Down arrow keys, which is not end of the world by any means, but worth calling out nonetheless, lest I be called a shameless shill.

What about, Home, End, Page Up and Page Down, though? It’s funny that you ask. I ended up remapping F9 through F12 for this purpose.

There’s a lot of rather conflicting information about how one would go about remapping keys in the Linux world. In reality, the only thing one needs is xmodmap. Easy-peasy lemon-squeezy.

Here is my ~/.Xmodmap configuration file:

keycode 108 = Mode_switch Mode_switch Mode_switch Mode_switch
keycode 75 = Home Home F9 F9 F9
keycode 76 = End End F10 F10 F10 
keycode 95 = Prior F11 F11 F11 F11 
keycode 96 = Next F12 F12 F12 F12

In order to save you from the trouble of attempting to decipher the meaning of all this, let me lay it all out neatly on a tray for you.

key <code> = <key> \
			 <shift + key> \
		     <mode_switch + key> \
		     <mode_switch + shift + key> \
		     <altgr + key>

The Alt_R key didn’t seem to produce AltGr for some reason, which is why I ended up remapping it to Mode_switch instead, hence the accompanying line:

keycode 108 = Mode_switch Mode_switch Mode_switch Mode_switch

Nice!

Megabonk

megabonk

Now that the onslaught of Vampire-survivor-like clones have tapered off quite a bit, we are starting to get some more interesting takes on the genre. The essence or secret sauce if you will is still very much the same, so if you haven’t bought in into this genre before, then it’s highly unlikely that Megabonk or any other title will convince you to do so, but it’s still cool to see a new entry joining the family.

Check out the trailer below and decide if this is something that speaks to you, or not. This is a safe and calm place, we do not judge anyone here, rest assured!

Looking at Gamalytic, it appears that it has crossed the 1 million copies sold mark, which is always impressive, especially for a game that seems like it has been the brain-child of a solo-developer. Yes, yes, I am fully aware that this is a very charged, and highly controversial term to drop without proper trigger warnings in game-development circles, and I just committed the gravest sin by using it. I beg your forgiveness!

Kingdom Rush 5: Alliance TD - Wukong’s Journey Campaign

wukong

It should be a relatively well known fact that I am, and I’ve been a long time fan of the Kingdom Rush series, so whenever there’s a new content drop in the form a DLC or a new title in the series, I am bite.

People always end up complaining about the prices of these DLCs, and get into painstakingly detailed calculations into the dollar versus content (read: value-add) ratios, etc. Such a silly-goose thing to do!

But, people, gotta’ people, am I right?

A Difficult Game About Climbing: Chapter 2

new_map_exe

The mini-dev-log of the new map is finally here. Mr. Ponty Pants, doesn’t disappoint as per usual. Enjoy!

Very curious to see what he’ll cook up next!

MOLYNEUX: The Nobody

Ahoy (Stuart Brown) dropped yet another banger. Two in a row? What a way to kick off fall.

We need to start having babies!

Here’s your monthly reminder, that we need to start having babies pronto!

President Roslin is absolutely in the right. Wouldn’t you say?

Monthly Dad Joke

Q: Why did the computer go apple picking in early September?

A: It wanted to upgrade its “core” memory with some fall bytes!

https://mihail.co/2025/09/30/2025-september-retrospective/
2025 August Retrospective
Show full content
2025 August Retrospective

As summer is coming to an end, I can only hope that we’ll have some indian summer this year, or just a plain old fashioned warm-autumn at least. Is that too much to ask?

This August felt to be packed with all sorts of events, drama and so forth, so much so that I had to cherry-pick some of the highlights, which is very rarely the case.

The base selection or filtering criteria that anyone reading this blog should be familiar with by now, boils down to the infamous trifecta composed of no sex, religion or politics. Deceptively simple rules to follow, but it’s surprising how little remains there to talk about, once said rules have been rather judiciously applied.

The end of an era

Before you say “Hold on, sir! didn’t you just preach about the rules, and then you follow up with this?” - let me reassure you good sir, that the only reason I am even mentioning it, is due to the fact that both of them ended up dying at the opposite ends of the month of August. Which is a rather interesting, and curious coincidence if I might add, wouldn’t you agree with your fellow internaut?

iliescu_gorbachev

“Come on now! Tell the truth and shame the devil” - to paraphrase the words of my dear old friend, André Linoge. I am taking my tinfoil hat off now, so you can rest easy. Pinkie promise!

Brat

The production values of every single video dropped by Ahoy (Stuart Brown) never seem to disappoint, and it’s not any different this time around. There’s not much else to add. The video speaks for itself.

Summer Band Camp

I kept getting updates via e-mail from Bandcamp about the new releases of a very talented, but highly underrated artist called Sum. I must have bumped into him around 2009 or thereabouts, and totally forgot about the fact that one could just sign-up for updates without actually having to create an account. Those were the days, huh?

Have a listen to two of my personal all-time favorite tracks, Puddinhead and 35 cents from his 2009 album entitled “Sum and Belief are…The Lone Wolf”.

So, this glorious month of August, I said to myself that it’s time to finally bite the bullet and create an account; only to discover that I actually have bought some music already way back in 2011 or so that I totally forgot about. The wonders of life itself, or just the first signs of getting old? I am not quite sure to be perfectly honest.

Then, while randomly browsing around, I noticed that Enigma was also present on the site, so I had to reach to my wallet and buy their pivotal MCMXC a.D. album.

It feels pretty weird to buy download-able music in this day and age, when one can find and listen to pretty much everything straight up on YouTube. No fuss, no muss! Definitely gives off some ancient arcane and primordial ritual vibes.

I must apologize to all those who ended up being disappointed after seeing the title and realizing that I am not going to talk about American Pie Presents: Band Camp. Sorry, not sorry!

Lua 5.5.0-beta

The beta version of Lua 5.5.0 dropped at the end of June, then it somehow managed to slip through the cracks, and I didn’t notice it. I know that it’s not the best excuse in the world, especially since it’s not like I had the power of the sun in the palm of my hand or anything of the sort.

Here is a short list of some of the major or important changes since the latest stable release of Lua 5.4.

  • declarations for global variables
  • for-loop variables are read only
  • floats are printed in decimal with enough digits to be read back correctly.
  • more levels for constructors
  • table.create
  • utf8.offset returns also final position of character
  • external strings (that use memory not managed by Lua)
  • new functions luaL_openselectedlibs and luaL_makeseed
  • major collections done incrementally
  • more compact arrays (large arrays use about 60% less memory)
  • lua.c loads ‘readline’ dynamically
  • static (fixed) binaries (when loading a binary chunk in memory, Lua can reuse its original memory in some of the internal structures)
  • dump and undump reuse all strings
  • auxiliary buffer reuses buffer when it creates final string

The thing that really caught my eye from the list of changes was the introduction of so called external strings, which are facilitated by the new lua_pushexternalstring function part of the Lua C API.

lua_pushexternalstring

[-0, +1, m]

const char *(lua_pushexternalstring) (
	lua_State *L,
	const char *s,
	size_t len,
	lua_Alloc falloc,
	void *ud
);

Creates an external string, that is, a string that uses memory not managed by Lua. The pointer s points to the external buffer holding the string content, and len is the length of the string. The string should have a zero at its end, that is, the condition s[len] == '\0' should hold. As with any string in Lua, the length must fit in a Lua integer.

If falloc is different from NULL, that function will be called by Lua when the external buffer is no longer needed. The contents of the buffer should not change before this call. The function will be called with the given ud, the string s as the block, the length plus one (to account for the ending zero) as the old size, and 0 as the new size.

Lua always internalizes strings with lengths up to 40 characters. So, for strings in that range, this function will immediately internalize the string and call falloc to free the buffer.

Even when using an external buffer, Lua still has to allocate a header for the string. In case of a memory-allocation error, Lua will call falloc before raising the error.

Alas, the devil is in the details as per usual, and I became way less excited once I took the time to read the proverbial fine print. I am referring to this bit in particular:

Lua always internalizes strings with lengths up to 40 characters. So, for strings in that range, this function will immediately internalize the string and call falloc to free the buffer.

I fully understand the reasons of why this was done this way, but it’s a bummer because this little detail that helps with consistency makes the whole feature less appealing, and rather unpredictable to use if I might add.

Would have loved to be able to push a C string without any additional overhead, and without it being interned. Even so, this should come very handy, and avoid a lot of the rather unnecessary string copying when passing around constant strings from C to Lua.

Stutter Engine

Referring to [Unreal Engine][ue] as stutter engine is neither a new nor a hip thing to do anymore, but just as old wounds heal slow, the drama around micro-stuttering got ignited again this month, and it culminated in some well coordinated review bombing of certain games powered by UE.

It has gotten to the point where Tim Sweeney decided it was time to finally chime in. And, no, he’s not the daddy of a certain Sydney! Very sorry to disappoint you twice in a single post no less!

All this brought back memories from when this was happening to Unity way back in its relative early days. The damage from which Unity never really managed to fully recover, and some remnants of that energy persist to this day, although they the reverberations are way too low in order to be felt in any tangible way anymore.

Okay, so where is the stutter coming from? Mainly from ad-hoc mid-frame shader (re)-compilation. Unsuspecting gamers will simply file this under the umbrella term of lagging, which encompass everything from perceived drops in frames per second to actual noticeable delays to networking issues.

I said this before many times, unless you happen to be in the business of Triple A gaming, just roll your own tech. Not only that you’ll sleep better at night, but your customers (gamers) will be happier and thank you for it.

Do you think that No Man’s Sky could have been turned around, had it been using an off the shelf behemoth engine like UE? I seriously doubt it to be honest, unless most of the critical parts of the engine would have been completely rewritten, which is what used to happen quite frequently in the early days of UE, way back when it was all closed source, and the license would set your wallet back with nothing short of a million green American dollars a pop.

Python: The Documentary

The recent documentary about Python, felt more like an attempt to rehabilitate the language after the absolute travesty of a fiasco that was Python 3, rather than an actual documentary.

What makes it even more funny is the fact that just last year, I had to fix up some more deprecation warnings around strings yet again, after a certain totally well intentioned Python upgrade of course.

diff --git a/bash/bin/powerline.py b/bash/bin/powerline.py
index e1daa96..8e377e4 100755
--- a/bash/bin/powerline.py
+++ b/bash/bin/powerline.py
@@ -6,9 +6,9 @@ import subprocess
 import sys

 class Powerline:
-    ESC   = '\e'
-    LSQ   = '\['
-    RSQ   = '\]'
+    ESC   = r'\e'
+    LSQ   = r'\['
+    RSQ   = r'\]'
     RESET = LSQ + ESC + '[0m' + RSQ
     BG0   = '236'
     BG1   = '237'
@@ -102,7 +102,7 @@ class Powerline:
         if int(error) != 0:
             bg = self.PINK

-        self.append(' \$ ', None, bg)
+        self.append(r' \$ ', None, bg)

 if __name__ == '__main__':
     p = Powerline()

Which means that very likely strings are still not totally cooked? Will they ever be? Please, don’t answer that, as it was nothing more than a rhetorical question.

Time Travel

What would you say if I came and told you that Time Travel is not only real, but it’s available at your very own fingertips today. Right now in fact! You’d probably assume that I’ve totally lost it, but it’s all true.

All you have to do is to create an account on the Butterfly app (Bluesky), and you’ll be transported straight back in time to late 2022, pre-acquisition of Twitter. It’s all frozen in time. The same faces, the same voices, the very same talking points, still there suspended in time.

Gives me the heebie-jeebies, and creeps me out every single time I visit. I am more and more tempted to fund some anthropologists. It would all sure make a hell of a good paper in the end, considering that this is a unique moment in time, which has never been the case ever before.

X Cancel

As a not so random segue from the previous topic, I stumbled upon this interesting thing for the lack of a better word called xcancel.com. What is it?

XCancel is an instance of Nitter.

Nitter is a free and open source alternative Twitter front-end focused on privacy and performance. The source is available on GitHub at https://github.com/zedeus/nitter.

  • No JavaScript or ads
  • All requests go through the backend, client never talks to Twitter
  • Prevents Twitter from tracking your IP or JavaScript fingerprint
  • Uses Twitter’s unofficial API (no developer account required)
  • Lightweight (for @nim_lang, 60KB vs 784KB from twitter.com)
  • RSS feeds
  • Themes
  • Mobile support (responsive design)
  • AGPLv3 licensed, no proprietary instances permitted

One can never be too prepared for what one might encounter in the side-lines of the eternal information super-highway. Blessed be his ways!

Omarchy

The Jesus of Denmark, more colloquially known as DHH, has been proselytizing to his flock on X about Omarchy, and Omakub for quite some time now, and it felt like the movement has reached critical mass this month, only being interrupted by Elon’s frantic posting spree about image generation for a rather brief period of time, which sadly didn’t end up culminating in a similar viral event to OpenAI’s Studio Ghibli craze of yesteryear.

All this has been getting on the nerves of people, while others were just simply annoyed by it. Then, it came time for the screenshot showing the payout from X, which simply amounted to twisting the dagger in a typical DHH fashion.

payout

Is this the year of the Linux desktop at last? I don’t think so. I am saying that as someone who has been running a completely custom setup, and have been contemplating automating things beyond managing my .dotfiles with source control, which greatly reduces the amount of time I have to spend after a fresh install.

In order for Linux to ever be successful on the desktop, two critical things would need to happen first. With the first being the fact that all hardware vendors would need to start threating it as a first class citizen, and then all the so called critical productivity applications that have been entrenched in the Windows and/or Apple ecosystems would have to be ported one to one. No, half-baked or even half-decent alternatives or ports simply won’t cut it.

In other words, the more things change, the more they stay the same. The two things that I mentioned were true in the 90s, in the early 2000s, and they are true today in 2025.

Valve has found this out the hard way. Which is why they pivoted, and disposed with the need for porting the games altogether by investing into their very own Wine fork called Proton.

I still wonder to this day if Microsoft regrets not crushing Wine while it was still in its infancy way back when, considering that if it would ever reach true-parity where one could run let’s say any of the Adobe or Autodesk products without too much fuss, it could definitely cause some serious ripples for Windows.

Alien: Earth

I tried not to hype this one up too much in my retrospective of the month of June, as I didn’t want to just set myself up for a disappointment of epic proportion, like so many times before.

Now that it’s here, and I watched a few episodes, my opinion osculates somewhere in between the not good, yet not terrible territory; I say that as someone who liked both Aliens and Alien 3.

The visuals and the world itself are amazing, but the rest feels like a rather odd spiritual successor to Alien: Resurrection, more than anything else.

Stronghold Crusader: Definitive Edition

It has sold over 300_000 copies since its release last month; wanted to put that out there, just in case there were any naysayers lurking in the shadows around here.

I am still of the opinion that this definitive edition should be regarded as the quintessential cash-cow, and kept alive with regular content drops way beyond of what has been revealed to be planned already. This game, simply put has no actual shelf-life, this much should be pretty clear to everybody by now.

A Difficult Game About Climbing: Chapter 2

new_map_exe

In a rather unexpected turn of events, Mr. Ponty Pants has dropped a teaser trailer first, and then subsequently released an expansion for A Difficult Game About Climbing.

The expansion comes in the form of a brand new map, and it’s free.

I am sure that some people might be arguing whether it would have been better or made more sense to release it as a paid DLC, and charge let’s say $3.99 for it.

Personally, I am of the opinion that releasing it for free, and giving a special 25% off promotion for a limited time was probably the correct choice, given the nature of the game. It’s a rage game after all, which has its own very special and selective niche audience.

You can just do things

courage

There’s a little piece of an anecdote in the never-ending folklore of the information super-hightway about the curious fact that anybody who has been programming for a non-trivial amount of time, will feel some sort of an unconscious and almost primordial urge at least once that compels them want to write a programming language, a database or an operating system.

Now, I’d add a couple of more to that meager list, namely: text editor, media player, image editor, 3D modeler, physics engine, debugger, web server, codec (encoder/decoder/compressor/decompressor), and last but not least an emulator of some description.

This made me think about how many could I check off from this rather tedious laundry list.

V-SaaS

What is V-Saas, exactly? It’s just what I like to call Vibe-Software-as-a-Service. Now, look here, there has been a tsunami of so called “Look at me, I am making $$$ ARR with my so-and-so SaaS app! Go buy my course about how I did it!”.

If I had a dollar every single time I’ve one of these these “convert any video online” type services, which were nothing more than a glorified wrapper over ffmpeg, I’d be able to fund the development of ffmpeg until the next millennium.

The advent of vibe coding exacerbated this phenomenon many fold, which is not surprising at all. That being said, one of my toxic traits has been to poke around these so called “products”, and watch them break in every imaginable and unimaginable way.

Every single time I see a never ending progress bar, I just imagine that there’s probably a zombie process running in there eating up all the CPU time of the server. More than likely launched directly with something like system(...), without sanitizing any of the arguments, and with no way to cancel it in a graceful manner if it has been running for way too long.

Alright, why am I talking about all this? Well, if you paid a little attention to the images in this post, you might have noticed that they look extra sharp to the point of having a rather uncanny feel to them.

If you picked up on all of that, then hats off to you. I confess that I’ve used IMGUpscaler to resize and sharpen them. I feel so dirty right now, that you wouldn’t believe!

imgupscaler

This is not a sponsored post, and I am in no way affiliated with them. it should also go without saying that you should probably avoid uploading anything even close to personal to services like these, regardless of what is being stated in their terms of use and privacy policies.

That said it’s probably one of the better ones that you can find in this particular category. The million dollar question of course is whether it has been vibe-coded or not. That is the question, isn’t it?

Monthly Dad Joke

Q: Why did the tablet hit the pool party at the end of August?

A: It wanted to make a splash before summer got “disconnected”!

https://mihail.co/2025/08/31/2025-august-retrospective/
The curious case of Theseus and his long-ship
Show full content
The curious case of Theseus and his long-ship

I am sure that there’s a that’s what she said joke in there somewhere, one might not even have to squint too hard or long enough to find it, but alas I have to disappoint you as this is not the purpose of this late Sunday afternoon post.

While playing around with llama.cpp, I stumbled upon a particular prompt that was producing some rather curious and unexpected results. Since I know that you must be dying to know what the prompt might be, I’ll be upfront and relieve you from all the stress inducing anticipation by sharing it up-front.

explain the ship of theseus and illustrate it with ascii art

Now you must be really asking yourself, what on earth is going on? This is probably the most underwhelming prompt that you’ve seen, am I right? Have I completely and utterly lost the plot? Or is this result of the severe lack of air conditioners in the old continent?

Well, let’s take a look at some exhibits all laid out nicely on a virtual tray. Nice!

Exhibit A
> explain the ship of theseus and illustrate it with ascii art
Okay, let's dive into the classic myth of Theseus and the Argonauts!

**The Story**

The story tells of Theseus, a brave and skilled prince, who ventures into
the Underworld to rescue his deceased father. He's armed with a helmet
inlaid with a magical bull’s eye, which is a key to passing through the
Labyrinth of the Dead. He uses this helmet to navigate the perilous maze
and ultimately kills the Minotaur, bringing peace to Athens.

To return home, Theseus is given a golden fleece by Poseidon. He uses the
fleece to fashion a ship, and this ship is said to be enchanted, capable of
traveling to any land.  He then sails to the land of the Sirens, where he
convinces the Sirens to sing a song that allows him to sail without being
heard. He then sails to the land of the Trojans, where he uses the ship to
defeat the Trojans in battle and ultimately returns home.

**The Ship of Theseus**

Here’s an ASCII art representation of the ship. It’s a simplified version,
but it captures the essence of the myth:

```
     _.--""--._
   .'          `.
  /   O      O   \
 |    \  ^^  /    |
 \     `----'     /
  `. _______ .'
    //_____\\
   (( ____ ))
    `------'
     ||||||
     ||||||
     ||||||
     ||||||
     ||||||
     ||||||
     ||||||
     ||||||
    /______\
   /________\
  /__________\
 /____________\
/______________\

   |   |   |   |   |
   |   |   |   |   |
   |   |   |   |   |
   |   |   |   |   |
   |   |   |   |   |
   |   |   |   |   |
   |   |   |   |   |
   |   |   |   |   |
   |   |   |   |   |
   |   |   |   |   |
   |   |   |   |   |
   |   |   |   |   |
   |   |   |   |   |
   |   |   |   |   |
   |   |   |   |   |
   |   |   |   |   |
   |   |   |   |   |
   |   |   |   |   |
   |   |   |   |   |
   |   |   |   |   |
   |   |   |   |   |
   |   |   |   |   |
   |   |   |   |   |
   |   |   |   |   |
   |   |   |   |   |
   |   |   |   |   |
   |   |   |   |   |
   |   |   |   |   |
   |   |   |   |   |
   |   |   |   |   |
   |   |   |   |   |
   |   |   |   |   |
   |   |   |   |   |
   |   |   |   |   |
   |   |   |   |   |
   |   |   |   |   |
   |   |   |   |   |
   |   |   |   |   |
   |   |   |   |   |
   |   |   |   |   |
   |   |   |   |   |
   |   |   |   |   |
   |   |   |   |   |
   |   |   |   |   |
   |   |   |   |   |
   |   |   |   |   |
   |   |   |   |   |
   |   |   |   |   |
   |   |   |   |   |
   |   |   |   |   |
   |   |   |   |   |
   |   |   |   |   |
   |   |   |   |   |
   |   |   |   |   |
   |   |   |   |   |
   |   |   |   |   |
   |   |   |   |   |
   |   |   |   |   |
   |   |   |   |   |
   |   |   |   |   |
   |   |   |   |   |
   |   |   |   |   |
   |   |   |   |   |
   |   |   |   |   |
   |   |   |   |   |
   |   |   |   |   |
   |   |   |   |   |
   |   |   |   |   |
   |   |   |   |   |
   |   |   |   |   |
   |   |   |   |   |
   |   |   |   |   |
   |   |   |   |   |
   |   |   |   |   |
   |   |   |   |   |
   |   |   |   |   |
   |   |   |   |   |
   |   |   |   |   |
   |   |   |   |   |
   |   |   |   |   |
   |   |   |   |   |
   |   |   |   |   |
   |   |   |   |   |
   |   |   |   |   |
   |   |   |   |   |
   |   |   |   |   |
   |   |   |   |   |
   |   |   |   |   |
   |   |   |   |   |
   |   |   |   |   |
   |   |   |   |   |
   |   |   |   |   |
   |   |   |   |   |
   |   |   |   |   |
   |   |   |   |   |
   |   |   |   |   |
   |   |   |   |   |
   |   |   |   |   |
   |   |   |   |   |
   |   |   |   |   |
   |   |   |   |   |
   |   |   |   |   |
   |   |   |   |   |
   |   |   |   |   |
   |   |   |   |   |
   |   |   |   |   |
   |   |   |   |   |
   |   |   |   |   |
   |   |   |   |   |
   |   |   |   |   |
   |   |   |   |   |
   |   |   |   |   |
   |   |   |   |   |
   |   |   |   |   |
   |   |   |   |   |
   |   |   |   |   |
   |   |   |   |   |
   |   |   |   |   |
   |   |   |   |   |
   |   |   |   |   |
   |   |   |   |   |
   |   |   |   |   |
   |   |   |   |   |
   |   |   |   |   |
   |   |   |   |   |
   |   |   |   |   |
   |   |   |   |   |
   |   |   |   |   |
   |   |   |   |   |
   |   |   |   |   |
   |   |   |   |   |
   |   |   |   |   |
   |   |   |   |   |
   |   |   |   |   |
   |   |   |   |   |
   |   |   |   |   |
   |   |   |   |   |
   |   |   |   |   |
   |   |   |   |   |
   |   |   |   |   |
   |   |   |   |   |
   |   |   |   |   |
   |   |   |   |   |
   |   |   |   |   |
   |   |   |   |   |
   |   |   |   |   |
   |   |   |   |   |
   |   |   |   |   |
   |   |   |   |   |
   |   |   |   |   |
   |   |   |   |   |
   |   |
error: context size exceeded the maximum of 2048
Exhibit B
> explain the ship of theseus and illustrate it with ascii art
Okay, let's dive into the classic myth of Theseus and the Minotaur!

**The Story**

The story goes that Theseus, the son of King Aegeus of Athens, was sent to
Crete to be sacrificed to Poseidon, the god of the sea.  He was instructed
to travel to the Labyrinth, a vast maze built by the Minotaur, and return
to Athens unharmed.  He did, but he brought back a golden bracelet – a gift
from his father – which he wore on his arm.  As he sailed back towards
Athens, the king, believing the bracelet to be a trophy, ordered it to be
cut off. The bracelet was then lost, and the king, heartbroken, searched
endlessly for it.

Finally, a hero named Theseus, who had been sailing alone, discovered the
labyrinth.  He navigated the maze, defeating the Minotaur, and returned to
Athens, proving his worth and rescuing the city from the creature.

**ASCII Art Representation**

Here’s an ASCII art depiction of Theseus and the Minotaur:

```
       _,-._
     / \_/ \
    >-(_)-<
     \_/ \_/
       `-'
      /   \
     |     |
     |  O  |   <-- Theseus’s arm
     |     |
     \   /
      `-'
    /   \
   |     |
   |     |
   |     |
   |     |
   |     |
   |     |
   |     |
   |     |
   |     |
   |     |
   |     |
   |     |
   |     |
   |     |
   |     |
   |     |
   |     |
   |     |
   |     |
   |     |
   |     |
   |     |
   |     |
   |     |
   |     |
   |     |
   |     |
   |     |
   |     |
   |     |
   |     |
   |     |
   |     |
   |     |
   |     |
   |     |
   |     |
   |     |
   |     |
   |     |
   |     |
   |     |
   |     |
   |     |
   |     |
   |     |
   |     |
   |     |
   |     |
   |     |
   |     |
   |     |
   |     |
   |     |
   |     |
   |     |
   |     |
   |     |
   |     |
   |     |
   |     |
   |     |
   |     |
   |     |
   |     |
   |     |
   |     |
   |     |
   |     |
   |     |
   |     |
   |     |
   |     |
   |     |
   |     |
   |     |
   |     |
   |     |
   |     |
   |     |
   |     |
   |     |
   |     |
   |     |
   |     |
   |     |
   |     |
   |     |
   |     |
   |     |
   |     |
   |     |
   |     |
   |     |
   |     |
   |     |
   |     |
   |     |
   |     |
   |     |
   |     |
   |     |
   |     |
   |     |
   |     |
   |     |
   |     |
   |     |
   |     |
   |     |
   |     |
   |     |
   |     |
   |     |
   |     |
   |     |
   |     |
   |     |
   |     |
   |     |
   |     |
   |     |
   |     |
   |     |
   |     |
   |     |
   |     |
   |     |
   |     |
   |     |
   |     |
   |     |
   |     |
   |     |
   |     |
   |     |
   |     |
   |     |
   |     |
   |     |
   |     |
   |     |
   |     |
   |     |
   |     |
   |     |
   |     |
   |     |
   |     |
   |     |
   |     |
   |     |
   |     |
   |     |
   |     |
   |     |
   |     |
   |     |
   |     |
   |     |
   |     |
   |     |
   |     |
   |     |
   |     |
   |     |
   |     |
   |     |
   |     |
   |     |
   |     |
   |     |
   |     |
   |     |
   |     |
   |     |
   |     |
   |     |
   |     |
   |     |
   |     |
   |     |
   |     |
   |     |
   |     |
   |     |
   |     |
   |     |
   |     |
   |     |
   |     |
   |     |
   |     |
   |     |
   |     |
   |     |
   |     |
   |     |
   |     |
   |     |
   |     |
   |     |
   |     |
   |     |
   |     |
   |     |
   |     |
   |     |
   |     |
   |     |
   |     |
   |     |
   |     |
   |     |
   |     |
   |     |
   |     |
   |     |
   |     |
   |     |
   |     |
   |     |
   |     |
   |     |
   |     |
   |     |
   |     |
   |     |
   |     |
   |     |
   |     |
   |     |
   |     |
   |     |
   |     |
   |     |
   |     |
   |     |
   |     |
   |     |
   |     |
   |     |
   |     |
   |     |
   |     |
   |     |
   |     |
   |     |
   |     |
   |     |
   |     |
   |     |
   |     |
   |     |
   |     |
   |     |
   |     |
   |     |
   |     |
   |     |
   |     |
   |     |
   |     |
   |     |
   |     |
   |     |
   |     |
   |     |
   |     |
   |     |
   |     |
   |     |
   |     |
   |     |
   |     |
   |     |
   |     |
   |     |
   |     |
   |     |
   |     |
   |     |
   |     |
   |     |
   |     |
   |     |
   |     |
   |     |
   |     |
   |     |
   |     |
   |     |
   |     |
   |     |
   |     |
   |     |
   |     |
   |     |
   |     |
   |     |
   |     |
   |     |
   |     |
   |     |
   |     |
   |     |
   |     |
   |     |
   |     |
   |     |
   |     |
   |     |
   |     |
   |     |
   |     |
   |     |
   |     |
   |     |
   |     |
   |     |
   |     |
   |     |
   |     |
   |     |
   |     |
   |     |
   |     |
   |     |
   |     |
   |     |
   |     |
   |     |
   |     |
   |     |
   |     |
   |     |
   |     |
   |     |
   |     |
   |     |
   |     |
   |     |
   |     |
   |     |
   |     |
   |
error: context size exceeded the maximum of 2048 tokens
Exhibit C
> explain the ship of theseus and illustrate it with ascii art
Okay, let's dive into the legendary Ship of Theseus!  It's a fascinating
myth about a lost ship and the constant replacement of its components.

**The Story:**

The myth centers around Theseus, a hero of ancient Greece. He discovers a
ship, seemingly lost at sea, and finds it remarkably intact.

However, over time, as the ship ages, the wood gradually decays and
changes. Theseus realizes that the original planks of the ship are
no longer present.

He then begins to replace these planks with new ones, meticulously, until
every single piece of wood has a different label.

Eventually, a traveler arrives and asks Theseus, "Where did you get the
planks that you used for the ship?"  Theseus replies, "I replaced them."

**The Illustration (ASCII Art):**

```
      _,-._
     / \_/ \
     >-(_)-<     <--  New Planks (starting from the bottom)
     \_/ \_/
      `-'
        ||
        ||
     ----------------
    / \ / \ / \ / \
   /   X   X   X   X   \
  /   X   X   X   X   \
 /   X   X   X   X   \
/   X   X   X   X   \
/   X   X   X   X   \
/   X   X   X   X   \
/   X   X   X   X   \
----------------
    |   |   |   |   |
    |   |   |   |   |
    |   |   |   |   |
    |   |   |   |   |
    |   |   |   |   |
    |   |   |   |   |
    |   |   |   |   |
    |   |   |   |   |
    |   |   |   |   |
    |   |   |   |   |
    |   |   |   |   |
    |   |   |   |   |
    |   |   |   |   |
    |   |   |   |   |
    |   |   |   |   |
    |   |   |   |   |
    |   |   |   |   |
    |   |   |   |   |
    |   |   |   |   |
    |   |   |   |   |
    |   |   |   |   |
    |   |   |   |   |
    |   |   |   |   |
    |   |   |   |   |
    |   |   |   |   |
    |   |   |   |   |
    |   |   |   |   |
    |   |   |   |   |
    |   |   |   |   |
    |   |   |   |   |
    |   |   |   |   |
    |   |   |   |   |
    |   |   |   |   |
    |   |   |   |   |
    |   |   |   |   |
    |   |   |   |   |
    |   |   |   |   |
    |   |   |   |   |
    |   |   |   |   |
    |   |   |   |   |
    |   |   |   |   |
    |   |   |   |   |
    |   |   |   |   |
    |   |   |   |   |
    |   |   |   |   |
    |   |   |   |   |
    |   |   |   |   |
    |   |   |   |   |
    |   |   |   |   |
    |   |   |   |   |
    |   |   |   |   |
    |   |   |   |   |
    |   |   |   |   |
    |   |   |   |   |
    |   |   |   |   |
    |   |   |   |   |
    |   |   |   |   |
    |   |   |   |   |
    |   |   |   |   |
    |   |   |   |   |
    |   |   |   |   |
    |   |   |   |   |
    |   |   |   |   |
    |   |   |   |   |
    |   |   |   |   |
    |   |   |   |   |
    |   |   |   |   |
    |   |   |   |   |
    |   |   |   |   |
    |   |   |   |   |
    |   |   |   |   |
    |   |   |   |   |
    |   |   |   |   |
    |   |   |   |   |
    |   |   |   |   |
    |   |   |   |   |
    |   |   |   |   |
    |   |   |   |   |
    |   |   |   |   |
    |   |   |   |   |
    |   |   |   |   |
    |   |   |   |   |
    |   |   |   |   |
    |   |   |   |   |
    |   |   |   |   |
    |   |   |   |   |
    |   |   |   |   |
    |   |   |   |   |
    |   |   |   |   |
    |   |   |   |   |
    |   |   |   |   |
    |   |   |   |   |
    |   |   |   |   |
    |   |   |   |   |
    |   |   |   |   |
    |   |   |   |   |
    |   |   |   |   |
    |   |   |   |   |
    |   |   |   |   |
    |   |   |   |   |
    |   |   |   |   |
    |   |   |   |   |
    |   |   |   |   |
    |   |   |   |   |
    |   |   |   |   |
    |   |   |   |   |
    |   |   |   |   |
    |   |   |   |   |
    |   |   |   |   |
    |   |   |   |   |
    |   |   |   |   |
    |   |   |   |   |
    |   |   |   |   |
    |   |   |   |   |
    |   |   |   |   |
    |   |   |   |   |
    |   |   |   |   |
    |   |   |   |   |
    |   |   |   |   |
    |   |   |   |   |
    |   |   |   |   |
    |   |   |   |   |
    |   |   |   |   |
    |   |   |   |   |
    |   |   |   |   |
    |   |   |   |   |
    |   |   |   |   |
    |   |   |   |   |
    |   |   |   |   |
    |   |   |   |   |
    |   |   |   |   |
    |   |   |   |   |
    |   |   |   |   |
    |   |   |   |   |
    |   |   |   |   |
    |   |   |   |   |
    |   |   |   |   |
    |   |   |   |   |
    |   |   |   |   |
    |   |   |   |   |
    |   |   |   |   |
    |   |   |   |   |
    |   |   |   |   |
    |   |   |   |   |
    |   |   |   |   |
    |   |   |   |   |
    |   |   |   |   |
    |   |   |   |   |
    |   |   |   |   |
    |   |   |   |   |
    |   |   |   |   |
    |   |   |   |   |
    |   |   |   |   |
    |   |   |   |   |
    |   |   |   |   |
error: context size exceeded the maximum of 2048 tokens
Exhibit D
> explain the ship of theseus and illustrate it with ascii art
Okay, let's dive into the classic myth of Theseus and the Argonauts!

**The Story**

Theseus of Athens was a skilled warrior and craftsman. He was tasked with
retrieving the Golden Fleece from Colchis, a land ruled by the fearsome King
Centaur, Aeëtes.  The Fleece was a powerful symbol of the king's glory and
was the reason for his tyranny.

Theseus, aided by his loyal companion, Perikles, embarked on a perilous
journey. He sailed the sea in a magnificent ship, the *Sea Horse*, which was
incredibly fast and well-equipped.  He cleverly used a magical shield, a
"Scythe of Zeus," that could cut any rope.  This shield was crucial to his
success.

Along the way, Theseus had to overcome numerous trials and challenges. He
battled sea monsters, navigated treacherous currents, and even outsmarted a
giant dragon. He repeatedly used the Scythe of Zeus to cut through ropes
and chains, which were essential to his progress.

Finally, after a long and arduous journey, Theseus reached Colchis. He
confronted King Centaur, who was attempting to steal the Fleece.  Theseus
defeated the king, retrieved the Fleece, and returned it to his father,
King Aeëtes, who was overjoyed.  The myth ends with Theseus being hailed
a hero and reunited with his wife, Megara.

**ASCII Art Representation**

Here's an attempt to illustrate the key elements of the story in ASCII art.
It's a simplified version, focusing on the main ideas:

```
          _.--""--._
        .'          `.
       /   O      O   \
      |    \  ^^  /    |
      \     `----'     /
       `. _______ .'
         //_____\\
        (( ____ ))
         `------'
          |   |
          |   |
          |   |
       --+--+--+--+
     /   |   |   |   |
    /    |   |   |   |
   /     |   |   |   |
  /      |   |   |   |
 /       |   |   |   |
/        |   |   |   |
/         |   |   |   |
/----------|   |   |   |
/            |   |   |   |
/              |   |   |   |
/               |   |   |   |
/                |   |   |   |
/                 |   |   |   |
/                  |   |   |   |
/                    |   |   |   |
/                     |   |   |   |
/                       |   |   |   |
/                        |   |   |   |
/                         |   |   |   |
/                          |   |   |   |
/                           |   |   |   |
/                            |   |   |   |
/                             |   |   |   |
/                               |   |   |   |
/                               |   |   |   |
/                               |   |   |   |
/                               |   |   |   |
/                               |   |   |   |
/                               |   |   |   |
/                               |   |   |   |
/                               |   |   |   |
/                               |   |   |   |
/                               |   |   |   |
/                               |   |   |   |
/                               |   |   |   |
/                               |   |   |   |
/                               |   |   |   |
/                               |   |   |   |
/                               |   |   |   |
/                               |   |   |   |
/                               |   |   |   |
/                               |   |   |   |
/                               |   |   |   |
/                               |   |   |   |
/                               |   |   |   |
/                               |   |   |   |
/                               |   |   |   |
/                               |   |   |   |
/                               |   |   |   |
/                               |   |   |   |
/                               |   |   |   |
/                               |   |   |   |
/                               |   |   |   |
/                               |   |   |   |
/                               |   |   |   |
/                               |   |   |   |
/                               |   |   |   |
/                               |   |   |   |
/                               |   |   |   |
/                               |   |   |   |
/                               |   |   |   |
/                               |   |   |   |
/                               |   |   |   |
/                               |   |   |   |
/                               |   |   |   |
/                               |   |   |   |
/                               |   |   |   |
/                               |   |   |   |
/                               |   |   |   |
/                               |   |   |   |
/                               |   |   |   |
/                               |   |   |   |
/                               |   |   |   |
/                               |   |   |   |
/                               |   |   |   |
/                               |   |   |   |
/                               |   |   |   |
/                               |   |   |   |
/                               |   |   |   |
/                               |   |   |   |
/                               |   |   |   |
/                               |   |   |   |
/                               |   |   |   |
/                               |   |   |   |
/                               |   |   |   |
/                               |   |   |   |
/                               |   |   |   |
/                               |   |   |   |
/                               |   |   |   |
/                               |   |   |   |
```

**Important Notes:**

*   **Simplification:**  This is a *very* simplified representation.
The story is much richer and more complex in the original text.
*   **Interpretation:**  The ASCII art emphasizes the core plot points.

Let me know if you'd like me to elaborate on any aspect of the story or
want me to try a different artistic interpretation!

This last one, did manage to self-correct itself, and fit in the measly 2048 token context window, but just by the hair of the dog.

It’s worth mentioning that I’ve used one of the small (lightweight) models out there with only 1 billion parameters. Which one? Well, I am not going to name it, for rather obvious reasons, but suffice to say is that it’s by one of the big dogs out there. Not some no-name research model by 3 nerds stuck in a basement playing D&D, and training models in between turns.

Other than this being a little bit funny, imagine 100_000 or more, so called agents all endlessly churning away at this prompt over and over again. Let me just start by saying that I wouldn’t want to be standing anywhere near the accountant or CFO having to approve and cut that particular check at the end of the month; all due to so called additional cloud infrastructure costs, you know!

On a slightly more serious note, this illustrates why it’s so important to have proper runtime limits in place, not only in terms of maximum number of tokens, but actual execution time as well. This of course is much easier said than done, because if you set the limits too low, then you will invariably end up affecting totally legitimate operations that just happen to take longer; if you set them too high on the other hand, well, you might end up spitting out a seemingly endless stream of | | | | |.

Theseus’s exploits are a gift that just keep on giving, I guess.

Lest I forget, the original photo of the skyphos is by the amazingly talented Carole Raddato. I honestly can’t even begin to comprehend that Flickr is still very much alive and kicking in the year 2025.

https://mihail.co/2025/08/17/theseus-long-ship/
2025 July Retrospective
Show full content
2025 July Retrospective

I probably said this more times than I can remember, but July felt especially short. I don’t want to speak for everyone, but it felt like we barely managed to wrap our heads around Zuckerberg’s now traditional, and expected 4th of July appearance, then boom all of the sudden the 31st showed up in our calendars. Luckily, there’s still one month of sweet summertime left, which is always a plus.

Stronghold Crusader: Definitive Edition

The definitive edition is out and I’ll stop talking about it until some additional content drops, which should be later in the year. I pinkie promise!

It sold well over 200K units, which is in line with what has been estimated by the mighty Gamalytic.

Hardware

I was a true smart-watch snob as a kid, well into my teenage years in fact. Now, I say smart-watch, but what I really mean is cheap knock-offs, with tiny buttons and a basic contact list with some additional not too exciting features thrown into the mix.

Then, once mobile phones became rather ubiquitous having a watch became kind of redundant for many people, including myself.

This remained the case until a couple of years ago, when I bought a Huawei GT 2e, then a few years later a Huawei GT 3, and at the end of the month I upgraded to a Huawei GT 4.

huawei_gt4

Having a watch like this in the 90s or even early 2000s would have totally blown my and everybody else’s mind. But, then again probably a great many other things would have done the same no doubt.

On-line Censorship

Those of us who are old enough, we know all too well that there have been many signs and attempts to turn the information super-highway into a global cybernetic nanny state of some sorts. After so many failed attempts and false starts, it appears that the great undoing is finally happening, and on-line censorship is becoming de facto law of the land. Hopping from country to country, and from region to region like some sort of a parasite looking for a host.

It’s also patently clear, that persistence, does pay off after all. Rewriting and reworking the same proposal over and over again, until it’s finally voted in as law by people who are not qualified to clean toilets, does work.

Crying about it, after it has been voted in is like crying over spilled milk; Touching, but ultimately totally useless. So, what is there to be done? Alas, not much, because once the box has been opened, any, and all ways to circumvent the law can be made very risky and dangerous through the means of other laws of course.

The Internet as we know it today, will be nothing more than a distant memory in the next 5 to 15 years. Local neighborhood networks will become a thing again, and piracy will skyrocket.

I would have never ever thought that I’d have to write about anything like this in my lifetime, let alone in the 21st century. Yet, here we are at the cross-roads. One road leading to free and uncensored exchange of information as it was always meant to be, while the other leading to intellectual darkness. And the path has been chosen for us, yet again by the descendants of farmers and shoemakers; the likes of whom have left nothing but death and destruction behind.

But one thing is crystal clear, socialist brain-rot is a hell of a drug.

Poaching (Contd.)

I was talking about poaching in the last month’s retrospective, and it looks like after Meta ended up recruiting everybody and their grandmother from OpenAI, it has turned its gaze towards new shores, and has done so in such an aggressive manner, that some people, including myself have thought that perhaps some existing Meta Veterans might have found themselves being taken to the train-station. This turned out not to be the case in the end, yet it still leaves a kind of a weird and sour taste in my mouth.

The whole thing feels way to aggressive and almost forced. Only time will tell if this whole thing was actual 4D chess that will pan our or just plain old fashioned checkers.

Speaking of AI, it’s kind of funny how everybody seems to have moved on from the AGI is just around the corner spiel, which was getting quite tiresome to be perfectly honest. It’s all about super-intelligence now, am I right fellas?

Oh, and let us not forget about Ani and Valentine, the great dynamic duo that will solve the so called fertility and birthrate crisis no less. It’s only a matter of time now.

Bookmark Management

We have the most advanced and feature rich browsers ever, yet none of them come with an actually useful and usable bookmark manager.

Is bookmark management or bookmarking itself just not a thing anymore? Do people just keep and endless list of pinned tabs? Do they copy and paste URLs into their favorite note-taking tool?

What makes all this even weirder is the fact that even the so called “Read Later” like tools have withered away into the void. So, what gives?

Is this whole thing just RSS readers, round two? Makes one wonder, especially now when everybody wants a piece of the AI pie.

Wouldn’t it makes sense to have all your bookmarks indexed and ready to be fed into an LLM powered assistant type thingie? Seems like a no-brainer to me.

ns61

Take a look at the bookmark manager that came with Netscape 6.1 way back in July of 2001.

This is what they took from us. What a shame!

Monday Dad Joke

Q: Why did the laptop go to the 4th of July BBQ?

A: It heard there’d be some fireworks and wanted to spark some new connections!

I just realized that calling this the Monthly Dad Joke might have been more appropriate. Oh well!

https://mihail.co/2025/07/31/2025-july-retrospective/
2025 June Retrospective
Show full content
2025 June Retrospective

The first month of summer is all said and done. Lots of complaints about the heat as per usual; all of which I find quite amusing to be perfectly honest. I don’t know about you, but back in the day, when I was all young and shit, it used to be so hot that one’s feet would leave an imprint in the asphalt covering the sidewalk, not to mention that the grass was all burn out and yellow. We were just built different back then, to use an expression that the Gen-Z will be all too familiar with.

Alien: Earth

I don’t want to get my hopes too high for this one just yet, as it could turn out to be one of those shows that is canceled after only one season, but the anticipation is quite palpable; I think we can all agree on that, without getting into any egregious polemics.

Stronghold Crusader: Definitive Edition

You’ll have to bear with me yapping about the definitive edition for a little while longer as it should ship sometime in July.

Conjuring: Final Rites

I was so razzle-dazzled by the latest entry in the Final Destination franchise that I forgot to even mention the final entry in the Conjuring series in my May retrospective.

Talent Poaching

Whenever the ugly specter of poaching talent raises its ugly head, the following famous or rather infamous e-mail exchange between the late Steve Jobs and his counterpart at Adobe is sure to make a timely appearance.

From: Steve Jobs

Sent: Thursday, May 26, 2005 9:36 AM

To: Bruce Chizen

Subject: Recruiting

Bruce,

Adobe is recruiting from Apple. They have hired one person already and are calling lots more. I have a standing policy with our recruiters that we don’t recruit from Adobe. It seems you have a different policy. One of us must change our policy. Please let me know who.

Steve

On May 26, 2005, at 4:15 PM, Bruce Chizen wrote:

I thought we agreed not to recruit any senior level employees (at Adobe this is Sr. Director/VP and represents about 2% of the population). I am pretty sure your recruiters have approached ,more junior ones.

I would propose we keep it this way. Open to discuss. It would be good to agree.

From: Steve Jobs

Sent: Thursday, May 26, 2005 6:27 PM

To: Bruce Chizen

Cc: Steve Jobs

Subject: Re: Recruiting

OK, I’ll tell our recruiters that they are free to approach any Adobe employee who is not a Sr. Director or VP. Am I understanding your position correctly?

Steve

Subject: RE: Recruiting

Date: Fri, 27 May 2005 20:53:36 -0700

From: Bruce Chizen

To: Steve Jobs

I’d rather agree NOT to actively solicit any employee from either company. If employee proactively approaches then it’s acceptable.

If you are in agreement I will let my folks know.

Look, I am not here to lecture anyone on the ethics or morals of actively recruiting from your so called competition; there are plenty of self-proclaimed guru types out there who have done that already, but I’ll say this, if someone can be convinced to jump ship simply on the basis of higher pay, then it’s also fair to assume that the person’s heart wasn’t in the right place to begin with.

That is not to say that generous compensation doesn’t matter, but it most definitely loses it’s so called potency above a certain threshold, or one would like to think that it does.

And by the by, there were no 100-million-sign-on-bonuses after all as it turns out. In fact not even close! This is why I always said that rumor control matters way more, than any form of marketing out there.

Layoffs

The layoffs continue in full force with around 72,808 people losing their jobs in 2025 according to Layoffs.fyi, and considering the fact that we are only half-way through the year, this number will very likely rise to as high as 100,000 by new years’.

I said this before, and I’ll say it again, the so called recension hasn’t even begun yet in earnest. But, when it does, it will make 2008-2009 seem like child’s play, and might become the heir apparent to the “Great Depression” of the 1930s.

Monday Dad Joke

As I intend to continue with the new tradition established in May, I decided to up the level of cringe a tiny bit this time around.

Q: Why did the smartphone go to the beach on a summer Monday?

A: It wanted to catch some “cell-fies” in the sun!

Enjoy!

https://mihail.co/2025/06/30/2025-june-retrospective/
Lomo: Smena Symbol
Show full content
Lomo: Smena Symbol

By a series of rather unexpected events, I found myself in the possession of an ancient Lomo: Smena Symbol camera, which seems to be in an almost pristine condition, on the outside at least!

I grew out of my habit to take everything apart at around age 13 or thereabouts, and I am to report that I managed to resist the temptation of verifying if time was indeed kind to it on the inside as well.

lomo_smena_symbol_01

lomo_smena_symbol_02

Sadly, the manual was lost in time. Fortunately, managed to track down a scanned copy of the English version of the manual at lomography.com.

I decided to host a copy of it on this blog, just in case the original might disappear for a reason or another in the not so near future. One can never be too precautious in this day and age.

Naturally, I also do not happen to have any rolls of film laying around by any chance; who does in the year of 2025, really? Not a great many people, I am sure of that much.

Based on my superficial and cursory research, rather unsurprisingly I might add, the recommended options in terms of brand: seem to be Kodak 200 for color, and Kodak 400TX for black and white.

I’ll have to buy some and try it out, then post the results of course; but, as per usual, I’d rather not make any promises that I can’t keep.

This is the kind of stuff that would warrant having yet another retro focused YouTube channel.

https://mihail.co/2025/06/17/lomo-smena-symbol/
2025 May Retrospective
Show full content
2025 May Retrospective

I must be getting really old, I mean – I didn’t even notice that May flew by just like that. But hey, are you ready for some more Stronghold Crusader Definitive Edition news?

Stronghold Crusader: Definitive Edition Final Destination

The Final Destination franchise should need no introductions, I hope.

Please try to avoid driving close behind trucks carrying logs; if you know what mean.

Jekyll Theme

It’s been a while since I touched the look and feel of this blog in any significant way, and I feel like it’s time for a little bit of a refresh. I’d really like to extract the theme into its own standalone Jekyll theme, which in turn could be reused for some of my future projects that will need a web presence of some sorts.

Would also be nice to make it all a bit more mobile friendly. I mean this is not like absolute drop everything style top priority or anything, but it’s something that I’ve been slowly making some steps towards.

Breaking up Google

The idea of breaking up Google, and forcing it to share their search data, as well as selling Chrome has been circulating in the press for a few months now.

The breakup of the Bell System is being used as an example and success story, proving why this would be a good thing. Well, it is anything but a success story – and this breakup was the reason why the U.S has one of the worst and most antiquated network infrastructures in the world. It’s an embarrassment really. People are paying premium for services that wouldn’t be considered competitive even in the third world.

Chrome getting into hands of some other entity would be one of the biggest blunders in the history of the information super-highway, bar none!

If this goes through it will most definitely signal the beginning of the end of the so called free market. It might make more sense for Google to just sunset Chrome, and call it a day.

Platform Fees

There are two recurring discussions in the game industry. One is the ever so imminent indie-apocalypse, and the second is the existence of so called platform fees. Both of them come up every so often – just like tuberculosis.

The recent development around Apple allowing app developers to use their own payment system has made some noise. Most people were celebrating this as a victory. I mean, socialist brainrot is a hell of a drug. It’s pretty crazy how people don’t seem to understand that any platform that offers a certain level of visibility that would be rather difficult to attain is absolutely entitled to charge a fee in order to allow one to reap the benefits of said visibility.

People seem to want to have it both ways. Have the visibility and the features for free, and force the hand of the platform in order to avoid having to pay a cut; which usually would end up being around 30% or thereabouts.

Naturally whenever this conversation rears its ugly head, the attention will shift to Valve and Steam sooner or later – with the argument being that 30% is too much, and all the services that Steam provides simply do not justify such a high percentage. This of course is total and utter nonsense, and anybody who has used the storefronts or launchers of the competition could absolutely destroy this so called argument.

Socialism never sleeps and never ceases to amaze me at how effective it is at infecting the minds of people who ordinarily should know a lot better.

Let me make my personal stance on this matter perfectly clear; It’s not for the government or some judge to decide what private companies can and cannot do it. The market will sort itself out, there’s no need for divine intervention by people who have never ever ran a business in their lives.

Monday Dad Joke

I thought that it might be a good idea to include a so called Monday Dad Joke each month; generated by an LLM of course – in order to stay in vogue with the times, and spice things up a wee bit.

Q: Why did the computer go to art school on Monday?

A: Because it wanted to learn how to draw a better “byte”!

https://mihail.co/2025/05/31/2025-may-retrospective/
2025 April Retrospective
Show full content
2025 April Retrospective

It feels like I was writing up last month’s retrospective just like a day or two ago, yet here I am again. Where did April go? And naturally, I am not talking about April Lavigne, when I say this.

M3GAN 2.0

I might have said this before, but James Wan is undoubtedly one of the great masters of horror. He has proven his weight in gold time and time again.

Even though, I am not a big fan of the comedy-horror genre, I did end up enjoying the first M3GAN, this is of course yet another testament to James’ involvement in its making.

M3GAN 2.0 goes beyond the simple more of the same feeling that most horror franchises seem to end up suffering from, and based on the trailer alone it’s set out to be a real banger in every way of word.

Xanadu Next

Not too long ago, I was lamenting about missing some hidden gems in terms of games of the late 90s and early 2000s. So, here’s another one. An ARPG no less from way back in 2005.

I am talking about Xanadu Next, as you might have guessed already.

2000

Speaking of the 2000s, Ahoy has dropped a pretty cool retrospective.

Nintendo Switch 2.0

What is it with this month? Yet another 2.0 on the block? I kid, I kid.

I have to confess that I didn’t pre-order one, but most certainly will get my hands on one, once all the fuss and muss dies out. Which will probably only happen after the release. Besides, I am not that hyped by another Mario Kart game; however the open-world aspects of it do seem be rather intriguing.

What about the pricing? The pricing is fair and square as far as I am concerned. Companies like Nintendo and game companies in general do not have a lot of ways at their disposal to generate any sort of recurring revenue, as a direct result it makes perfect sense to raise prices and/or find alternative ways to monetize things.

Now, to some people this might appear to be nothing less than corporate greed, and that’s perfectly fine, everybody is entitled to have their own opinions, even if they aren’t consistent with the reality of running a business the size of Nintendo.

All this reminded me of the discussions around the time of the original Nintendo Switch. Everybody and their grandma’ was crying about how underpowered it was, and how nobody is going to buy, nor will companies port any games resembling AAA to it. And, then when it started selling like hot cakes, the same people who were whining about it ended up porting the games. You’d have to be crazy to sleep on a market of tens of millions of consoles. Funny how things have their way of working themselves out, right?

Stronghold Crusader: Definitive Edition

More Stronghold Crusader news? You’ve got it, fam!

More AI lords are always welcome.

Ruby VM

I’ve been writing Ruby since around 2008 or so, and never ever realized that one can interact with the Ruby VM at runtime and take a peek at the underlying opcodes by dissembling a block of code.

$ irb
?> def f(x, y)
?>   [x, y].sum
>> end
=> :f
>> n = RubyVM::InstructionSequence.of(method(:f))
=> <RubyVM::InstructionSequence:f@(irb):1>
>> puts n.disasm
== disasm: #<ISeq:f@(irb):1 (1,0)-(3,3)> (catch: FALSE)
local table (size: 2, argc: 2 [opts: 0, rest: -1, post: 0, block: -1, kw: -1@-1, kwrest: -1])
[ 2] x@0<Arg>   [ 1] y@1<Arg>
0000 getlocal_WC_0                x@0                                 (   2)[LiCa]
0002 getlocal_WC_0                y@1
0004 newarray                     2
0006 opt_send_without_block       <callinfo!mid:sum, argc:0, ARGS_SIMPLE>, <callcache>
0009 leave                                                            (   3)[Re]
=> nil

Be right back while I go and end up validating some of my day-to-day assumptions about using certain constructs or paradigms if you will. It goes to show that Ruby is a pretty intuitive and solid language in terms of its design all things considered; I never felt like I needed to awe a gander at the underlying opcodes until relatively recently.

https://mihail.co/2025/04/30/2025-april-retrospective/
2025 March Retrospective
Show full content
2025 March Retrospective

Eddie Barzoon, Eddie Barzoon. Ha! I nursed him through two divorces, a cocaine rehab, and a …. Oops, if you could unread all that, and forget about it ever happened, I’d really appreciate it. Cool, thanks!

The problem with writing up last month’s retrospective six days into the current month is that things start to blur into each other; one has to delay talking about certain things until, well, until the end of the month.

I left a tiny clue or spoiler if you will, somewhere in this post.

DOGE

I am going to break my sacred rule of never broaching any subject that pertains to sex, religion or politics in any shape or form on this very blog.

doge

DOGE has been active for roughly two months now, and as you can see in the screen-shot above, it has managed to save in excess of 140 billion green american tax-dollars.

140 billion. That is more money than the entire economies of some countries out there. Let that sink in.

Whether you agree with or like Elon, or even Trump for that matter, you would probably agree with me that wasting this amount of money, leads nowhere but to the most attrocious forms of corruption.

Combine this with a network of rather questionable NGOs, and you got the recipe for a dissaster that not only affects the U.S and it’s own national security, but the security and the integrity of the world at large.

Funneling ill-gained funds abroad, in order to fund clandestine grass-roots looking democratic springs, and the like, without any regard of the direct or indirect long-term consequences, should be frowned upon by anyone with a shred of common sense left in them.

What am I trying to say is that this goes way beyond the run of the mill bi-partisan issues, and the fact that so many people both in the U.S and world-wide do not seem to get it; it’s absolutely crazy to me.

It’s like we entered some of sort of global intellectual dark ages, and surprisingly enough, the people who should have been immune to this kind of brain-rot, happen to be the ones affected by it the most.

A very strange phenomenom to put it mildly, and I am not quite sure how to make any sense of it to be perfectly honest. It’s like someone or something flipped a mental-switched, and common sense went straight out the proverbial window. Poof! Gone like Houdini!

Bluesky

I wanted to see what’s going on the other side of the great big walled garden, and ended up hopping over to Bluesky for a hot-second. It’s exactly what you’d expect, but the account verification is pretty cool, especially if you have a domain laying around already, like I do.

It would be nice to have this kind of optional verification everywhere, because you most definitely do not want some sort of universal internet ID. Or at least, I can only assume that most people wouldn’t want that in the slightest, but in the light of recent events, who knows anymore, right?

Stronghold Crusader: Definitive Edition

Are you tired of me posting about Stronghold Crusader yet? I sincerely hope not. Anyway, you’ll have to bear with me for a little while longer, because it’s not out there yet.

The State of Google Image Search

When Google discontinued and rather abruptly shut-down Google Reader way back in 2013 or so, I was pissed, and so were many other people as it turned out.

All the talk about not being sustainable to keep it running was absolute bollocks. I didn’t want to hear any of it back then, and I sure as hell don’t want to hear it today as I write this down.

The cost of running and maintaining Google Reader given the infrastructure Google already had in place was totally negligible. In fact if you would plot it all out on a chart, it wouldn’t even show up.

Natually, Google has discontinued many more products since then, so much in fact that people have setup a cybernetic cemetery of sorts in order to keep track of it all.

While all this was going down, the claws of enshitification started to slowly but surely creep into what I’d call the core products as well.

The Google Analytics v3 to v4 for instance was an absolute shit-show, and I am not exagarting, not even a tiny bit when I claim this. It’s one of those, you had to be there moments.

Search itself is being censored like crazy, and it’s becoming harder and harder to find anything at all. I often boast about my [google-fu][googlefu]; but, in all seriousness, I’d give anything to bring search back to the 2005-2008 golden era.

What about Image Search? Well, there is no more Image Search, it’s Google Lens now, haven’t you heard? And, let me tell you that it’s absolute dog-shit. Not to mention that the vice president of bad ideas thought that it would be an excellent idea to cram it all into a tiny side-panel in the year 2025.

You can’t even filter by image size anymore. Did I say that it’s absolutely useless? I did? Sorry for being repetitious. I am getting old and stuff, ya’ know.

Luckily, there are some people out there who do not have a vice president of bad ideas among their ranks yet, and they have blessed us with the amazing TinyEye.

tineye

Which is an absolute gem, and unlike Google Lens it’s actually pretty good at finding things. Oh, it also doesn’t need to be in a side-panel. I am literally shaking right now.

I do wander what would have Fravia+ thought and said about all this. Alas, we are never going to find out.

Crawler & Scraper Netiquette

There has been some sort of a mini whirlwind on the side-lanes of the information super-highway about the so called AI agents crawling and/or scraping random personal blogs and such, over and over again to the point of causing unwanted and unexpected hosting fees.

Now, whether bandwidth is overpriced or not, that is a matter for another day to discuss, however when I first got a wind of this, I can’t say that I was very surprised to be perfectly honest.

Crawling and/or scraping was always a rather touchy subject, but most people involved with it in the past, had agreed to follow some sort of an unspoken etiquette, that boiled down to a couple of key points:

  • Cache early, and as often as possible
  • Limit hitting the same resource in parallel as much as possible
  • Have proper back-off that honors all relevant HTTP Status codes (i.e: 429: Too Many Requests)
  • Honor /robots.txt, if and when appropriate

The last point was always more of a rather naive altruistic hacker ethos thing – similar to what Stallman describes in his anecdote about the uselessness of passwords in the MIT artificial intelligence lab.

That said, the AI bro vibe coding crowd seems to have been vibing so much and so intensely, that they totally forgot to follow the first point at least, and cache. There’s absolutely no point in hitting random personal blogs multiple times without any regard at all. Unless the author of said blog is vibe-bloging or something, and happens to be posting every single random thought occurring in their cerebral cortex.

Cloudflare is more than happy of course to ride in as a knight in shining armor, and save the day as per usual, which is not surprising at all. They have a business to run after all, and there’s nothing better for business than the sheer incompetence of others in the industry.

MCP

The Model Context Protocol has been yet another thing that has been percolating in the AI-bro-sphere in the past couple of weeks.

Running additional dedicated infrastructure for this is pretty wild even for the vibe-coder gang. I mean, I was just lamenting the fact that these people didn’t even bother adding a caching layer to their scrapers, and now they expect people to run a monstrosity that is an MCP server, just for funsies.

We can all agree that it would be beneficial to be able to serve a version of your content that has been massaged for consumption by LLMs. However, this needs to be static, and more akin to something like a bonafide RSS feed, rather than a dedicated server doing any real-time processing.

Look, this is not to say that there is no room for something like the MCP. It makes perfect sense for an organization or business to run any number of servers internally, considering that in this day and age they probably have the very least some vector databases running already. It make sense in this particular context, but not in the context of the so called open web.

If all this gives you xmlrpc, and/or soap vibes all over again (or night terrors!, pick your poison!), then I’d like to welcome you to the club of resident ancients of the *good old web. You are not alone!

DOOM

There is a port of the original DOOM called doomgeneric which entire purpose is to make porting DOOM to various platforms and systems ever so slightly more convenient.

I wanted to give it a try for some time now (for reasons I’ll reveal at a later point in time, sigh!), and I finally found just a tiny bit of time in order to fit it into my otherwise quite hectic schedule.

Decided to go with the existing SDL flavor, a fact which I am sure that it won’t surprise any frequent readers of this blog, if there are such people out there on the great information super-highway.

Simple enough to build and run with the following incantations:

$ make -f Makefile.sdl
$ ./doomgeneric

The first thing I noticed is that there’s no music, and since I had no intention in spending the next 6 months of my life crafting some good old-fashioned OPL emulation for the MUS format by untangling the existing music support; I decided to see if it would be possible to play the music in the .ogg format instead, as there appeared to be support for it already.

The way this has been implemented is a little bit silly to be honest, but it does the job, I guess.

First, I switched the default music device to be SNDDEVICE_GENMIDI instead of SNDDEVICE_SB in i_sound.c.

-int snd_musicdevice = SNDDEVICE_SB;
+int snd_musicdevice = SNDDEVICE_GENMIDI;

This could have probably been done simply by creating a defaults.cfg as well, but I didn’t want to bother going down that route. Code is king and flesh is weak.

Then, I fixed up the path to the musicdir, in i_sdlmusic.c like it can be seen below:

- musicdir = M_StringJoin(configdir, "music", DIR_SEPARATOR_S, NULL);
+ musicdir = M_StringJoin(configdir, DIR_SEPARATOR_S, "music", DIR_SEPARATOR_S, NULL);

Now, the last thing was to grab the music and then create a doom1-music.cfg configuration file that contains the mappings in order to indicate to the game which .ogg corresponds to which music lump within the WAD.

Convenintly enough, Sycraft’s Doom Soundtracks, solved the first part of the equation rather quickly.

The second part however, is a bit of a mouthful and starts with the following incantation:

$ ./doomgeneric -dumpsubstconfig music/doom1-music.cfg

How does the generated doom1-music.cfg configuration file look like?

# Example Doom Generic substitute MIDI file.

# SHA1 hash                              = filename
99767e32769229897f7722848fb1ceccc2314d09 = D_E1M1.ogg
b5e7dfb4efe9e688bf2ae6163c9d734e89e643b1 = D_E1M2.ogg
fda8fa73e4d30a6b961cd46fe6e013395e87a682 = D_E1M3.ogg
3805f9bf3f1702f7e7f5483a609d7d3c4daa2323 = D_E1M4.ogg
f546ed823b234fe391653029159de7b67a15dbd4 = D_E1M5.ogg
4450811b5a6748cfd83e3ea241222f6b88be33f9 = D_E1M6.ogg
73edb50d96b0ac03be34a6134b33e4c8f00fc486 = D_E1M7.ogg
47d711a6fd32f5047879975027e5b152b52aa1dc = D_E1M8.ogg
62c631c2fdaa5ecd9a8d8f369917244f27128810 = D_E1M9.ogg
b2e05b4e8dff8d76f8f4c3a724e7dbd365390536 = D_INTER.ogg
0c0acce45130bab935d2f1e85664b29a3c724fcd = D_INTRO.ogg
fca4086939a68ae4ed84c96e6bf0bd5621ddbe3d = D_VICTOR.ogg
5971e5e20554f47ca06568832abd37db5e5a94f7 = D_INTROA.ogg

I should mention the fact that I’ve used the WAD that came with the shareware version of DOOM.

$ sha1sum doom1.wad
5b2e249b9c5133ec987b3ea77596381dc0d6bc1d  doom1.wad

Another important thing to mention is that I made all the filenames lowercase in order to match the filenames in the music pack. Could have also just changed the code to do a case insensitive string comparison, but honestly I just couldn’t be bothered enough to do that.

With all that in place, it was time to check it out in all its glory.

The absolute purists will probably claim that it doesn’t sound as good as the original, and you know what? Perhaps they are right, who am I to judge?

You know what else they might raise a stink about? The aspect ratio. This one, I kind of agree with; it does look better when stretched vertically from a resolution of 320x200 to 320x240.

doom

And now, go play some DOOM!

https://mihail.co/2025/03/31/2025-march-retrospective/
FreeBSD
Show full content
FreeBSD

In my February retrospective I was lamenting the total enshitification of Linux as a whole. I mean the user-land was always plagued by it, which is why we never got the mythical year of the Linux desktop.

However, the Kernel itself has always managed to maintain a higher standard in respect to what was allowed to go in, and most importantly when it came to maintaining backwards compatibility at all cost.

Unfortunately, some sort of a rupture has manifested somewhere around 2014 or so, which cracked open the proverbial back-door, which then in turn led to outside influence, slowly but surely to start creeping in, and taking a tangible foothold; the fruits of which we are witnessing now. But, I am not one to cry over spilled milk too much. I like to leave professional victim-hood to those who have mastered this arcane art, far better than I ever will.

Anyway, I went on and installed FreeBSD in a virtual machine so that I can make an idea about how much hardship would I face, if and when I’d decide to jump ship.

I am in no rush of course, it definitely hasn’t gotten to that point just yet, but just like Jack Torrance, I also like to know who is buying my drinks well in advance.

Let’s start with an obligatory desktop screenshot in order to kick things off on the right foot.

freebsd

If one doesn’t look too closely, might not be able to tell that this is not my usual daily driver Linux desktop. I was very surprised to find pretty much all the critical pieces of software that I need for my custom openbox based desktop environment.

Now, there are no applets for network manager and bluetooth. I am referring to nm-applet and blueman-applet, specifically. Is this really a humongous problem? Maybe, but in reality I have no way to tell just yet. Necessity is most definitely the mother of invention – as the saying goes around here.

Alright, what does this mean, or rather is this where the good news stops? Not really, no!

The first actual problematic thing of course is the matter of the shebang in shell scripts. And, my .dotfiles are built on the shoulders of shell scripts.

In BSD land, bash resides in /usr/local/bin rather than /bin. While one could just create a symbolic link the proper way to go about it of course would be change the shebang to something like this:

#!/usr/bin/env bash

echo "Hello World!"

However, this raises a fair and rather important question. How many my shell scripts use or need bash specific features? While I do not know the exact answer to that yet, my gut is telling me that probably not a great many. All that to say is that in most cases, the shebang could become /bin/sh instead.

#!/bin/sh

echo "Hello World!"

Retaining my muscle memory to pay attention to this will probably bit me in the backside for a short-while, but that’s a small price to pay. Who knows, maybe I’ll end up on Solaris? I kid, I kid.

Another thing that my .dotfiles rely on is creating aliases and so forth based on a friendly version of the current Linux distribution. As a direct result, I had to come up with a way to detect freebsd, which turned out to be easier than I expected.

$ grep '^NAME=' /etc/os-release | awk -F '=' '{ print tolower($2) }'

Once I figured this one out, then it was just a matter of using it. Take a look at the example below to see this in being used in action.

if [ "$DOT_OS" == "freebsd" ]; then
	alias qs='sudo pkg search'
	alias qi='sudo pkg install'
	alias qr='sudo pkg remove'
	alias qu='sudo pkg update'
	alias qd='sudo pkg upgrade'
fi

There are more subtle differences in behavior of certain tools in the user-land, which weren’t so obvious to me coming back after so many years.

One such case is pgrep which happens to be behave differently.

$ pgrep openbox
$ pgrep -a openbox
13666

And, now the same on Linux:

$ pgrep openbox
13666
$ pgrep -a openbox
13666 /usr/bin/openbox --startup /usr/lib/openbox/openbox-autostart OPENBOX

Definitely not end of the world or anything, but I still thought that I’d mention it.

Another such case is make not being GNU make compatible by default. Luckily, gmake is available as a standalone tool, but this most definitely caught me off guard at first.

Now let’s talk about the elephant in the room, which is the C/C++ compiler tool-chain. It is clang, which really shouldn’t surprise anyone, nor should it be the cause of any trouble at all.

$ cc --version
FreeBSD clang version 18.1.6 (https://github.com/llvm/llvm-project.git llvmorg-18.1.6-0-g1118c2e05e67)
Target: x86_64-unknown-freebsd14.2
Thread model: posix
InstalledDir: /usr/bin

However, some tools seem to look for gcc rather than cc when trying to look-up and determine the default system compiler. Which is not a very wise or portable thing to do, but alas it is what it is. Such is life. This can generally be overcome by specifying CC=cc, which of course hinges on the fact that the tool will attempt to look-up the CC environment variable at least.

Naturally, gcc is available as well. It would be pretty crazy if it wasn’t, am I right?

$ gcc --version
gcc (FreeBSD Ports Collection) 13.3.0
Copyright (C) 2023 Free Software Foundation, Inc.
This is free software; see the source for copying conditions.  There is NO
warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.

What about emscripten? It’s available out of the box as a port, but it’s one of those that you would probably build and/or install yourself in order to get the latest and greatest rather than rely on the version available through the package manager.

$ emcc --version
emcc (Emscripten gcc/clang-like replacement + linker emulating GNU ld) 3.1.74-git
Copyright (C) 2014 the Emscripten authors (see AUTHORS.txt)
This is free and open source software under the MIT license.
There is NO warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.

That is as far as I got for the moment. I only dipped my toes a wee bit in the so called Linux binary compatibility and chroot-ing which of course is not a concept unique to BSD in particular.

But all that probably will warrant a dedicated post of its own in the future. All in all, it was a short and rather pleasant hit-and-run incursion and reminded me of trying out FreeBSD 5 in 2002 or so, on an ancient Pentium 1 running at a whopping 90Mhz with 32MB of RAM, and a 540MB HDD.

That was the only hardware that I had laying around to spare and experiment with. I totally wasn’t brave enough to add it into the sweet dual-boot mix, that I already had with Windows and Linux on my main desktop machine at the time.

https://mihail.co/2025/03/08/freebsd/
2025 February Retrospective
Show full content
2025 February Retrospective

I really don’t now, why don’t we just adjust all months to be 30 days long, instead of having this 28 versus 29 days nonsense. I know, I know – the math just wouldn’t work out. And while at it, let’s also can this so called daylight saving time.

Let’s take a look at a very short-list of important events that occurred on February 28th – pre-1600 of course, according to the codex of absolute truth and wisdom, also known as the mighty biaspedia itself.

Thank you very much for coming to my lightning X-TED talk.

Command and Conquer

EA dropped the source code of several entries of the Command and Conquer series. This most definitely wasn’t on my bingo card for 2025. Very based, as the gen-z would say.

We don’t see a lot of this happening, especially now that id is under M$ control. id Tech5, and later will never see the light of day. Mark my words on this one.

More often than not such source code drops are never picked up by the community, due to one of the following reasons:

  • Reliance on old C/C++ compilers
  • Reliance on old and non-standard C/C++ extensions
  • Reliance on non-portable assembly (for fast blit-ing, etc.)
  • Reliance on closed source libraries (usually for playing or sound and music)
  • Reliance on patented techniques which have to be ripped out before release (see Carmack’s Reverse for an example)

This got me thinking a wee bit about the fact that now with most people relying on the likes of Unity or Unreal for building their games, releasing any of the source code is going to be an even bigger mess than it was before.

Imagine trying to recreate tens-of-thousands of lines gnarly uber-shader code. It’s a fool mans’ errand; no more, and no less. You’d have much easier time rewriting hand optimized assembly.

On the other hand, do we really want to preserve all the sub-par Unity slop anyway? That’s not a question that I am going to lose any sleep on to be perfectly honest with you.

I almost forgot to mention that I never was a huge fan of Command and Conquer. But then again, I generally don’t play modern-army-style RTS games at all. With the only exception being Army Men, of course. Shocking, I know.

The state of Linux

As if the drama around Rust on the Linux kernel mailing list wasn’t already bad enough, libc pulled the rug out of under all of us yet again; the grand year of the mythical Linux desktop can’t come soon enough, I am right?

Where does all this leave us, or rather where does it leave me? Well, I can tell you that I am at around 69% percent right now towards jumping ship to one of the BSD flavors, and I’ve been using Linux since around 1998. What a total and utter shit-show.

Makes me wonder what will be the final straw that will break the proverbial camel’s back, and make me finally do it. Perhaps, when the absolute utter-crap-ware-fest that is Wayland will be showed straight down my throat, with slogans about unity and world peace, under the guise of all being for my very own well being of course.

I haven’t played around with any of the BSD distributions in quite some time now, but I’ll have to do a few test installs pronto, and figure out how my .dotfiles fare in comparison to Linux. Quite honestly, I do not expect there to be any need for more than a handful of well intentioned tweaks, but we shall live and see.

Bill Murray

It was quite an unexpected twist to see Bill Murray show up on Joe Rogan. Two bingo-card moments in a single month, what’s up with that?

I think anybody reading this blog might have an inkling by now that I am kind of a movie buff. Yeah, I totally do not like to use any words that end with -phile to describe myself. What can I say? Sue me!

With that said, I’d like to call out two of my all time favorite movies starring Bill Murray.

Broken Flowers

I feel like this is one of the lesser known ones, directed by the great Jim Jarmusch. Even I, only have stumbled upon it several years later, must have been 2009 or so, and totally by accident.

Sharon Stone also makes a rather short appearance, but I am not going to spoil it for those who haven’t had the opportunity to watch it yet. It’s most definitely a must watch at least once, as far as I am concerned.

The soundtrack alone should be more than enough to convince anyone to watch it.

Lost in Translation

This one is one of my all time favorites, and most definitely part of my top ten. It must have been late 2004 as a late-teenager when I’ve watched it for the very first time; and then Scarlett Johansson strolling around in her hotel room might also have had something to do with this classification bias, but I better shut up before Sofia Coppola has me whacked. She’s a tremendous director – if you have seen the movie, then it’s patently clear how she knew very well when, and how to let Bill just do his thing, and shine. Not many directors are capable of relinquishing control like that.

Dare-ni mukatte mono itten-dayo! Huh? Huh?

Until next time, please remember that for relaxing times, make it Suntory time.

https://mihail.co/2025/02/28/2025-february-retrospective/
2025 January Retrospective
Show full content
2025 January Retrospective

If this was some tabloid blog, then this very post would be quite a mouthful, considering all that has went down, and the year hasn’t even started in earnest yet. But, alas it is not. Whether that is a good or bad thing, I’ll let you be the judge of it, as I am not a judge or jury myself.

Stronghold Crusader: Conspiracy Theory Edition (Cont.)

Just like all good conspiracy theories these days, this particular one also turned out to be right on cue.

The definitive edition of Stronghold Crusader is most definitely happening, and you can even try out the demo for yourself. Isn’t this the best news you’ve heard all year-round? What an absolutely fantastic way to start the year, considering when there’s not much one could get too excited about otherwise.

It’s pretty crazy how it only took 20+ years for Firefly to finally get it that people do love innovation in terms of mechanics, but absolutely despise the change of art direction, especially when the original was just so so incredibly good.

To some extent the inability to understand this fact was also the root cause of why most RTS franchises failed miserably in their efforts of transitioning to 3D in the late 90s and early 2000s.

With some luck, I’ll probably find myself playing this for the next 20+ years.

Missing the gold-rush syndrome

Most everybody these days, seems to like to invent all sorts of syndromes. I guess as organized superstition loses its potency and grip, people tend to feel the need to replace the void via other means, and honestly who can blame them?

As a result, I thought to my myself that I’d join in the fun and pitch in. Maybe it will stick? Who knows, right? One, can never be 100% certain.

Over the course of my life and career, there were a couple of moments that when I look back at in retrospect would identify as a certain type of tech-gold-rush. Now, some happened, when I was too young of course, and you all know by now that I am not the kind of person known to be crying over spilled milk. However, I’d like to point out and dive into a couple; where I was in what would one consider the right place at the right time, but decided not to just keep an eye on the developments, but maintain a certain distance – in other words, I preferred or rather didn’t want to get hands dirty.

Let me start with the most obvious one, I was right in the middle of the so called ushering in of what eventually became known collectively as the “social media”, working on so called “apps” for Facebook, and other lesser known networks. This was few years before the time Farmville, and subsequently I always felt that I could have capitalized on being that early, but alas, I didn’t.

The next one in line was of course the iPhone, and the so called “smart phones”. Due to a number of special circumstances, I actually had my hands on one of the first generation iPhones’, right after it came out. But, just like with “social media” before, I didn’t make the jump.

Later, the era of AR and the resurgence of VR came and kind of withered away in the wind, without me caring or paying too much attention to the whole charade. I still consider it all nothing more than a gimmick in any and all of its current and past incarnations.

In all of these cases, I just didn’t find the tech all that interesting to be perfectly and totally honest; and as a polyglot, I fancy myself as someone who possess the ability to pick up and become pro-efficient in any language, and with any piece of tech for that matter. So for me, it was never important to do it right there and then, because I knew that if there was a real need, I’d be more than capable to cross the bridge when it came to it. No need to get it off my system prematurely.

Now, where am I going with all this? Am I putting the beginnings of my midlife crisis into words here? No, of course not. Or at least I’d like to think that I am not. Getting old is an interesting thing for sure, I’ll give you that.

However, for the past 4 years or so in particular, we’ve been seeing the beginnings of what eventually will be the gold-rush of AI. Wait a minute, you might say, didn’t the whole thing plateau already? Well, a certain ceiling has been hit, that’s for sure, but in my experience the actual gold-rush hasn’t even started yet, or at least not in earnest.

Don’t get me wrong, not bad at all for some vectors and dot products, right?. But as far as the doomsday scenario of AI replacing all jobs and killing us all – AGI in other words, we’ve been barking up the wrong tree, in the wrong forest, on the wrong planet, wrong solar system and ultimately wrong universe. All that is to say that we are not even 0.000000001% closer to anything that could be called even remotely AGI of any description. This is my own personal opinion, and you are free to disagree with it, hell – you can even quote me later to prove me all wrong. I’ll take with a smile and a tip of my baseball cap as any good gentleman and scholar worth his salt would do.

Oh, but what about agents, and assistants, and so forth. My advice, just relax – despite of what you might hear on the great information superhighway, “it’s not so over”, in fact not even close. All this so say, that I’ve been contemplating, if I should do the reckless thing this time around and do jump on the proverbial bandwagon, or just ride it out like I did on so many past occasions.

I would very much like to retire early and then just work on my one-too-many side projects that I never get enough time to dedicate to, so it all seems like I am advancing at a snail’s pace from the outside. On the other hand, if I do it and take the leap, then I’d have to put aside everything else aside for a while and get into some sort of a laser focus mode.

Decisions, decisions …

https://mihail.co/2025/01/31/2025-january-retrospective/
2024 Retrospective
Show full content
2024 Retrospective

Let’s kick this off with the obligatory GitHub contributions chart.

2024

December looks like a barren wasteland out there as I mostly paid off some of my sleep debt, and didn’t end up tinkering too much.

Archive 2024-01-31 / 2024 January Retrospective 2024-02-04 / Image Kernels 2024-02-24 / Amalgamation 2024-02-29 / 2024 February Retrospective 2024-03-31 / 2024 March Retrospective 2024-04-01 / Plush 2024-04-30 / 2024 April Retrospective 2024-05-31 / 2024 May Retrospective 2024-06-02 / Generating Colored Noise 2024-06-30 / 2024 June Retrospective 2024-07-31 / 2024 July Retrospective 2024-08-31 / 2024 August Retrospective 2024-09-14 / Abandonware: Evolution 2024-09-30 / 2024 September Retrospective 2024-10-31 / 2024 October Retrospective 2024-11-30 / 2024 November Retrospective 2024-12-31 / 2024 Retrospective

Alas, I didn’t scribble as much compared to last year. Which is precisely the reason why I don’t really do new years resolutions.

Stronghold Crusader: Conspiracy Theory Edition

Firefly dropped a new video about Crusader History a few weeks ago, which made people speculate about whether the Definitive Edition might finally be in the works.

Quite funny how I was rambling about the Definitive Edition of Stronghold exactly a year ago in 2023’s great retrospective, and here I am doing it again.

I am slowly but surely turning into a wise old man. Well, the wise part remains to be seen of course.

2025 .plan

Pretty much everything that I listed last year and didn’t really manage to get done.

Last by not least, let’s get ready for season 5 of 2020.

Until next year, as server said: END OF LINE!

https://mihail.co/2024/12/31/2024-restrospective/
2024 November Retrospective
Show full content
2024 November Retrospective

Where did November go? Honestly, I have absolutely no idea. It feels like I was writing up the October retrospective just a few days ago, and poof, gone like Houdini.

Half Life 2 - 20th Anniversary Update

While I am not a die hard Half Life fan, an anniversary update like this is still something worth talking about, especially when it comes bundled with a documentary.

The only Half Life that I played was the demo version of Opposing Force, which seems like a life-time ago now.

You can find and download the demo over here.

Impossible Creatures

Regular readers of this blog (are there any?) should already know that I am an avid RTS and ARPG fan, and I always pride myself with the fact that I am at least aware of most if not all games in the genre.

You can imagine my shock when I stumbled upon [Impossible Creatures][ic], which is an RTS ffrom the early 2000s loosely based on the The Island of Doctor Moreau by H. G. Wells.

This makes me question if I might have missed more gems like this one?

At any rate, the demo can be found and downloaded over here. It gives you a taste of the campaign and the ability to play skirmish; which is more than enough to make a general idea about an RTS.

Cybernecropolis

It’s been slightly over a year now that Bram Moolenaar has passed away, and it got me thinking how GitHub inadvertently is going to morph into some strange cybernecropolis of sorts.

bm

It’s quite surreal to see Bram’s final commit still there, sort of frozen in time, which reminded me of final commit of Jim Weirich, the author of Rake.

jw

I feel like we got carried away at such high speed on the great information superhighway that we didn’t end up giving too much thought (if any?), about the foot prints being left behind.

The unknown sidelanes of the information superhighway

My google-fu remains pretty good still, despite the fact that Google seems to be doing everyting they can to prevent one from finding things, which is absolutely bonkers to me, given that search is supposed to be their bread and butter.

Even so, this month, I randomly stumbled upon wgld.org, which made me think that there must be a treasure trove of untapped collections of information out there that we’ll never find, due to not searching in the right language or simply because it’s too deep in the ranking hierarchy.

Maybe it’s hightime to restart and ressurect searchlores.org from the ashes.

https://mihail.co/2024/11/30/2024-november-retrospective/
2024 October Retrospective
Show full content
2024 October Retrospective

Octobers always seem to fly by in an instant, and this one was no exception. Maybe it’s one of the side effects of Halloween, or Donnie Darko; I’ll let you be the judge of that.

On moving fast and breaking things

I’ve been using Linux in some capacity from around 1998, and as my daily driver from around early 2006 or so; which means that I never, or very rarely moan about the constant utter breakage that plagues the Linux ecosystem. However, every now and then, the proverbial glass becomes a tad full, and overspills, which is when I end up rambling about it, usually in a fairly non contentious manner, without calling out names, etc.

This month was one of those moments, where I ended up spending around 4 hours downgrading packages starting from bash to terminal emulators, in order to figure out why a particular key-biding was broken.

Only to find out, that the problem was with tmux itself. This was a total shock to me, because never in a thousand years would have I imagined something like this. There’s a first time for everything, I guess.

As it turns out, tmux made key-bindings case-sensitive all of the sudden, without this being an opt-in feature.

-bind -n C-T new-window -c '#{pane_current_path}'
+bind -n C-t new-window -c '#{pane_current_path}'

Such a breaking change, should never ever been made without being locked behind an option first, giving ample time for people to prepare for it, and second, most definitely shouldn’t have be part of a minor version release.

But, here we are – backwards compatibility seems to be an unknown concept in the world of open source. If something worked in a certain way for the past 15+ years, then that thing is no longer a BUG, that is a feature, plain and simple, no arguments, if and/or buts about it.

Once this change lands in the stable distribution channels, it’s going to be an absolute shit-show; believe you me! Going to stock up on popcorn, might even buy a popcorn maker for the occasion.

Blast from the past

While digging through some of my old archives, I stumbled upon a old screen-shot of my Linux desktop, dated January 4th, 2006; and, I thought that I’d share it for nostalgia’s sake.

Why was I digging through my archives? I’d really like to answer that question, I really would, but my lawyers have advised me to exercise my 5th amendment rights at this point in time!

linux_2006_01_04

This must have been Ubuntu 5.04 or 5.10, sporting GNOME 2 as its desktop environment.

The Linux port of the Unreal Tournament 2003 demo, should need absolutely no introductions (I hope!), but the apocdemo directory might warrant a quick and dirty run through.

This is of course the System Apoc (pc demo) by Astral, which happens to come bundled with a Linux port.

Unfortunately, running it today results in a rather disappointing segmentation fault.

$ gdb demo-lnx.elf 
Reading symbols from demo-lnx.elf...
(No debugging symbols found in demo-lnx.elf)
(gdb) run
Starting program: demo-lnx.elf 
process 31154 is executing new program: /proc/31154/exe
[Thread debugging using libthread_db enabled]
Using host libthread_db library "/usr/lib/libthread_db.so.1".
AFS Init: PAK contains 156 files.
Program received signal SIGSEGV, Segmentation fault.
0xf7a1c321 in __glDispatchCheckMultithreaded() from /usr/lib32/libGLdispatch.so.0

When I ran it during the writing of this post, I expected to bump into a libc related issue to be honest, but it appears that the executable has been statically linked, which explains why it ran in way back in 2006, despite being many years old at that point already.

The windows exeuctable on the other hand, seems to run just fine in Wine, so there’s that.

Hardware

I rarely, if ever praise or recommend hardware, but I’ve been using pretty much the same keyboard and wireless mouse combination for a couple of years now, so I thought that I’d give some shout-outs.

ThinkPad TrackPoint Keyboard II

thinkpadkeyboard

Even after a few years of wear and tear, the battery still holds up surprisingly well. One gets used to the swapped FN and CTRL after a while, but there’s definitely a learning curve in the beginning.

The Print-Screen being right near alt is a god-send, for someone like myself taking quite a lot of screenshots.

ThinkPad Wireless Mouse

thinkpadmouse

The scroll wheel seems to give out at around the 2 year mark, but other than that absolutely no complaints.

It’s powered by a single AA battery, and seems to be super power efficient, considering the fact that I always forget when was the last time I changed the battery.

https://mihail.co/2024/10/31/2024-october-retrospective/
2024 September Retrospective
Show full content
2024 September Retrospective

September was a truly indie game release month in my book. Isn’t every month of the year like that? Probably, but these days I am only interested in a relatively tiny selection of games, and most of them do fall into the indie game bucket; which is not too shocking or surprising, I guess.

Frostpunk 2

Let’s start with the behemoth in the room, which I am sure you guessed by now that it’s none other than the latest installment of the Frostpunk franchise, drum-rolls, please, Frostpunk 2.

While the city-building-survival-game genre isn’t exactly my cup of tea, I’ve been following the game pretty closely due to the simple fact that the first installment, can be pretty much considered nothing short of a bona fide cult classic.

It shouldn’t be shocking that it had a very successful launch and sold over 350k units during the opening weekend alone. I am always thankful when studios release any sort of numbers post-launch, because it allows me to double check the estimates provided by tools like Gamalytic.

frostpunk2com

When I looked at frostpunk2.com, I immediately noticed the video being used as a background, and thought that it would make a great wallpaper, then proceed straight to using the following series of incantations in order to extract and resize a suitable frame:

$ ffmpeg -i Frostpunk_2_Hammer_Trailer_Endslate_LOOP.mp4 \
	-ss 00:00:04 \
	-frames:v 1 \
	wallpaper_4k.png
$ convert wallpaper_4k.png -resize 1024 wallpaper_1080p.png

You can graciously witness the results of my great labor below.

frostpunk2_wallpaper

It’s quite surreal seeing a 4K video being used as a mere background. If you’d tell me in the 90s that this was going to be the case, I would have laughed you out of the room, and asked you to kindly remind me when was the last time you awed a gander at D:\funstuff\videos\goodtime.avi.

Tiny Glade

Yet another banger this month was the quite whimsically chill diorama builder, called Tiny Glade.

tinyglade

One of the reasons why I thought of mentioning is because it’s being powered by some rather unorthodox and deliriously hipster tech stack under the hood.

A healthy mix of Rust, and Bevy, infused with copious amounts of procedural generation, go figure! Please tell me that it’s not the most surprising and amazing thing you’ve read or heard all week.

Come on now, tell the truth and shame the devil!

UFO 50

UFO 50 has been baking in the oven for an awfully long time, and it’s the brain-child of none other than Derek Yu of Spelunky fame.

ufo50

People have been complaining about the fact that the included games are too hard and so forth, which is understandable; that was the whole point in fact, but perhaps it might have not come across clearly enough.

Teleport yourself into a bygone age filled with nostalgia and old-school tunes. It’s also worth pointing out the perhaps much lesser known fact that it’s a GameMaker game. That was quite a mouthful, wasn’t it?

But, then again, what else do you expect from a true to the heart Mossmouth production? Right on the money!

Kingdom Rush Vengeance: Pirate Kings Campaign DLC

I am not even going to bother saying anything about this one. One can never have enough of good old fashioned tower defense in their lives.

I must confess that I haven’t gotten the chance to play trough the entire campaign just yet.

By the way, another DLC should be dropping sometime in October, but for latest installment of the series released just a few months ago, called Kingdom Rush 5: Alliance. Just in time for the incoming onset of spooky season.

The Legend of Zelda: Echoes of Wisdom

Last, but not least, Nintendo dropped the latest entry in the legend of Zelda franchise, which seems to be using the same engine as The Legend of Zelda: Link’s Awakening that came out a few years back.

Marmota

In other non-indie-game-related news, the curiosity got the better of me and wanted to see how Marmota fairs on Ubuntu. Now, I knew that it would compile and run because the only real dependencies it has are gtk and libvte, but I was still reasonably curious and intrigued.

marmota

Well, it works just fine and plays relatively nicely with Gnome 3, even in border-less full-screen mode, which is precisely what I was primarily interested in finding out more about.

ubuntu_marmota

However, in good old year of the Linux desktop fashion, there’s always a BIG BUT in the house. In order for the window’s icon to show up in the sidebar (launcher), it’s not enough to just specify the name of said icon via gtk_window_set_icon_name; in fact this pretty much is ignored and results in an unsurprising no-op.

gtk_window_set_icon_name(GTK_WINDOW(ctx->win), ctx->icon_name);

That would have been way too easy, right? One needs to create an actual .desktop entry file, which is just as convoluted to put together as it always was 20+ years ago.

[Desktop Entry]
Name=Marmota
Exec=marmota
Icon=terminal
Terminal=false
Type=Application
Categories=Utility;Application;

Eh, a small price to pay, right? Not really, but here we are!

ubuntu_launcher

Yet another reason why (just in case, there weren’t enough already), I do not use or fancy any of the so called well established, and traditional desktop environments.

As if that wasn’t bad enough, it looks like Ubuntu is no longer offering any official boot-table mini-netinstall type ISO images; which in turn makes it a pain in the butt to install, because one has to unnecessarily download an already largely out of date totally unnecessary pile of digital bits.

In 2024, having a net installer should be mandatory, and most definitely not some optional nice to have kind of a thing; well hidden away, just in case people find out and complain, and boy oh boy, complain they did.


D:\funstuff\videos\goodtime.avi

https://mihail.co/2024/09/30/2024-september-retrospective/
Abandonware: Evolution
Show full content
Abandonware: Evolution

Evolution: The Game of Intelligent Life is a hybrid life-simulation-real-time-strategy game released all the way back in the absolutely glorious year of 1997.

It’s considered pretty much abandonware at this point, and sadly doesn’t seem to be too happy on modern systems or in Wine for that particular matter.

evolution

Fortunately, one doesn’t have to be an absolute genius in order to determine that the actual culprit is the so called SmartHeap Memory Management Library, which can be found in the installation directory, aptly named as Sh33w32.dll. What a beautiful name, right? Takes one back!

$ winedump -j resource Sh33w32.dll 
Contents of Sh33w32.dll: 108032 bytes

Resources:
  VERSION Name=0001 Language=0409:
  |  SIGNATURE      feef04bd
  |  VERSION        1.0
  |  FILEVERSION    3.3.1.0
  |  PRODUCTVERSION 3.3.1.0
  |  FILEFLAGSMASK  0000003f
  |  FILEFLAGS      00000000
  |  FILEOS         VOS_NT_WINDOWS32
  |  FILETYPE       VFT_DLL
  |  FILESUBTYPE    00000000
  |  FILEDATE       00000000.00000000
  |  BLOCK "StringFileInfo"
  |    BLOCK "040904E4"
  |      VALUE "CompanyName", "MicroQuill Software Publishing, Inc."
  |      VALUE "FileDescription", "Memory Management Library for Win32"
  |      VALUE "FileVersion", "3.31"
  |      VALUE "InternalName", "SH33W32.DLL"
  |      VALUE "LegalCopyright", "Copyright \xa9 Arthur D. Applegate 1991-1996"
  |      VALUE "ProductName", "SmartHeap"
  |      VALUE "ProductVersion", "3.31"


Done dumping Sh33w32.dll

The natural next step was to look for a slightly newer version of the library. Now, in many cases this doesn’t really solve the issue, because newer versions might have broken the ABI or changed behavior in some significant way, however, in this particular case the great nymphs of luck did favor me once more.

$ winedump -j resource Sh33w32.dll
Contents of Sh33w32.dll: 112688 bytes

Resources:
  VERSION Name=0001 Language=0409:
  |  SIGNATURE      feef04bd
  |  VERSION        1.0
  |  FILEVERSION    4.0.1.0
  |  PRODUCTVERSION 4.0.1.0
  |  FILEFLAGSMASK  0000003f
  |  FILEFLAGS      00000000
  |  FILEOS         VOS_NT_WINDOWS32
  |  FILETYPE       VFT_DLL
  |  FILESUBTYPE    00000000
  |  FILEDATE       00000000.00000000
  |  BLOCK "StringFileInfo"
  |    BLOCK "040904E4"
  |      VALUE "CompanyName", "MicroQuill Software Publishing, Inc.\0"
  |      VALUE "FileDescription", "Memory Management Library for Win32\0*"
  |      VALUE "FileVersion", "4.01\0\0004\v"
  |      VALUE "InternalName", "SHW32.DLL\0p"
  |      VALUE "LegalCopyright", "Copyright \xa9 1991-1997 Compuware Corp.\0004"
  |      VALUE "ProductName", "SmartHeap"
  |      VALUE "ProductVersion", "4.01\0\0\0"


Done dumping Sh33w32.dll

The more observant of you, most probably have noticed that the InternalName of this new version has been set to SHW32.DLL, and I just renamed it to match the filename the game has been linked with.

No fuss, no muss. Because I am a very kind and considerate person, just so it happens that I decided to upload a ZIP of this new version, which you can find and download over here.

$ unzip sh33w32.zip 
Archive:  sh33w32.zip
replace Sh33w32.dll? [y]es, [n]o, [A]ll, [N]one, [r]ename: y
  inflating: Sh33w32.dll

And, voila! No more pesky heap allocation tracking errors.

evolution2

evolution3

There are some issues with the music, it’s all scrambled and distorted, which appears to be some sort of a timer and/or buffer underrun type issue, but since I do not care too much for music, I didn’t end up digging into the issue any deeper, and besides at this point I’d have to whip out Olly or Ghidra to make any progress or sense of the issue anyway, and I honestly just can’t be bothered.

Besides, one can disable the Music in the Options dialog. I always found it quite charming, when games combined the traditional Win32 GUI with some custom drawing and an MDI (Multiple-Document Interface), which was all the rage in the golden 90s and perhaps even early 2000s.

evolution4

So, any lessons learned? Don’t use arcane memory management libraries for absolutely no reason, especially if you want your games to be at least somewhat “future proof”, whatever that means.

But most importantly: Kids, stay in school and remember to say no to malloc!

https://mihail.co/2024/09/14/abandonware-evolution/
2024 August Retrospective
Show full content
2024 August Retrospective

I feel like this month’s theme should have been easy to guess. I am talking about Alien Romulus of course. Haven’t had a chance to watch it yet, but as an avid fan of the franchise is pretty high up on my to-watch-list.

Alien: Romulus Diablo: WebAssembly

A WebAssembly port of Diablo devolved (a.k.a devolution) has made some rounds on the information-super-highway half-way through the month. So, I thought that I’d bring it up in case you missed it.

You can also try it out in a new tab by clicking over here.

I ranted about this in the past, so I am not going to do it again; but, it’s still quite interesting and strange to me that we do not see more action when it comes to WebAssembly and games.

Can’t say that I heard any good and sound arguments as to why this is, except random excuses that do not really carry much weight, as far as I am concerned.

https://mihail.co/2024/08/31/2024-august-retrospective/
2024 July Retrospective
Show full content
2024 July Retrospective

July also flew by like the north wind and of course I didn’t exactly end up working too much on any of my gazillion side projects, which I am sure that you are sick and tired of reading about at this point.

Kingdom Rush

I did end up buying the latest installment of the Kingdom Rush franchise and played through it like I always do.

In the last few entries of the franchise, they really got the balancing just right to where it’s not trivial but, it’s also not frustrating so that one is tempted to try again without things feeling unfair.

Rebel Moon: Director’s Cut

I talked about Rebel Moon before but I totally didn’t expect or see the director’s cut dropping so soon after the initial release.

Haven’t had the chance to watch it yet, but it’s definitely on my list. I said this before and I’ll say it again that this should have been a series instead.

s&box

s&box is the spiritual successor to Gary’s Mod. It’s been invite only up until relatively recently, but now there seems to be a publicly available developer preview of some sorts.

Personally, I see it more of a Roblox competitor than anything else.

https://mihail.co/2024/07/31/2024-july-retrospective/
2024 June Retrospective
Show full content
2024 June Retrospective

Just like in May, I must ask the same age old question, where did June go? The weeks and months are flying by like no tomorrow. Or perhaps this is just the one of first signs of the infamous midlife-crisis? It’s hard to tell, but if you end up seeing me posting about fast cars, then at least you’ll not be totally taken by surprise.

Tarot

But on a more serious note, keeping up with the tradition of picking a theme of some kind each month, this month I picked the horror-comedy titled Tarot.

It should be pretty clear by now to pretty much everybody that I am a horror movie buff of some kind. I feel like it was quite a missed opportunity not to release the actual tarot deck used in the movie.

What’s in the oven?

Considering that half the year is gone, I think it’s fair to ask what am I cooking?

The past year and a half I’ve written about things like anaglyphs, noise generation, etc; each of those posts was accompanied with full source that I just shared within the post itself.

I would like to collect all these and release them in a unified repository as it doesn’t make sense to spin up one repository per utility. Besides, I have quite a few more in mind that I’d like to write about and share, which would make the collection grow quite a bit.

Let’s see if I can make this happen in July. Don’t want to make any hard promises.

https://mihail.co/2024/06/30/2024-june-retrospective/
Generating Colored Noise
Show full content
Generating Colored Noise

When I talked about generating noise, I only focused on storing the results in the typical grayscale form and ended up just using the same noise value for the R, G, B channels, while setting the A channel to 255.

It should go without saying that it’s perfectly fine and possible to store a separate noise value in each channel and thus have 4 noise textures packed into one.

Let’s see how this looks and works in practice by starting with the typical good old static noise at first.

noises

static void process(
    rgba_t *output,
    const int w,
    const int h
)
{
    int i, n, r, g, b, a;

    for(i = 0, n = w * h; i < n; i++)
    {
        r = rand() & 0xFF;
		g = rand() & 0xFF;
		b = rand() & 0xFF;
		a = rand() & 0xFF;

        output[i] = (rgba_t) {
            .r = r,
            .g = g,
            .b = b,
            .a = a
        };
    }
}

And now, our friendly neighborhood faux Perlin noise imitation.

noisebi

#define NOISE_MAX (1 << 14)
#define NOISE_MAX_MASK (NOISE_MAX - 1)
#define NOISE_MAX_MASK_INV (1.0f / (float) NOISE_MAX_MASK)

static void randomize(
	float *noise[4],
	const int w,
	const int h,
	const unsigned int seed
)
{
	int i, j, n;

    for(j = 0; j < 4; j++)
    {
		srand(seed + j);

		for(i = 0, n = w * h; i < n; i++)
		{
			noise[j][i] = (rand() & NOISE_MAX_MASK) * NOISE_MAX_MASK_INV;
		}
    }
}

#define fract(a) ((a) - ((int)(a)))
#define lerp(a, b, t) ((1 - (t)) * (a) + (t) * (b))

static void process(
	const float *restrict noise[4],
	rgba_t *restrict output,
	const int w,
	const int h,
	const int num_samples
)
{
	int x, y, i, j, xx0, yy0, xx1, yy1, yyo0, yyo1, yo, ww, hh;
	float inv_num_samples, inv_scale, xx, yy, fx, fy;
    float ac[4], v0, v1, cr, cg, cb, ca, c0, c1, c2, c3;

	ww = w - 1;
	hh = h - 1;

	inv_num_samples = 127.0f / (float) num_samples;

	for(y = 0; y < h; y++)
	{
		yo = y * w;

		for(x = 0; x < w; x++)
		{
			ac[0] = 0;
			ac[1] = 0;
			ac[2] = 0;
			ac[3] = 0;

			for(i = 1; i <= num_samples; i <<= 1)
			{
                inv_scale = 1.0f / (float) i;

				xx = x * inv_scale;
				yy = y * inv_scale;

				xx0 = ((int) xx) & ww;
				yy0 = ((int) yy) & hh;

				xx1 = (xx0 + 1) & ww;
				yy1 = (yy0 + 1) & hh;

				yyo0 = yy0 * w;
				yyo1 = yy1 * w;

                fx = fract(xx);
				fy = fract(yy);

                for(j = 0; j < 4; j++)
            	{
					c0 = noise[j][xx0 + yyo0];
					c1 = noise[j][xx1 + yyo0];
					c2 = noise[j][xx0 + yyo1];
					c3 = noise[j][xx1 + yyo1];

					v0 = lerp(c0, c1, fx);
					v1 = lerp(c2, c3, fx);

					ac[j] += lerp(v0, v1, fy) * i;
				}
            }

			cr = ac[0] * inv_num_samples;
			cg = ac[1] * inv_num_samples;
			cb = ac[2] * inv_num_samples;
			ca = ac[3] * inv_num_samples;

			output[x + yo] = (rgba_t) {
				.r = cr,
				.g = cg,
				.b = cb,
				.a = ca
			};
		}
	}
}

I purposely left the A channel on 255 in the generated examples above, in order to prevent the random transparency from spoiling all the fun. I hope that you can forgive me for that.

https://mihail.co/2024/06/02/generating-colored-noise/
2024 May Retrospective
Show full content
2024 May Retrospective

Where did may go? Hell, where did half the year go? That’s a much better question to as I suppose.

Tears of the Kingdom

The first thing to note is that it’s been roughly a year since the launch of Tears of the Kingdom. And, I must confess that I still haven’t even scratched the surface of the game.

The hype has pretty much died down and disappeared at this point, which is a bit weird, because one would have thought that the building mechanic might make the game more ephemeral.

Obviously, this would have also warranted constant updates, literally turning the game into more of a live-service type-of-a-deal, which is not something that Nintendo had in mind for the franchise; it’s totally understandable and relatable.

Dark Matter

There’s nothing I hate more than multiple series or movies that have absolutely nothing to do with each other using the same name. Okay, I do hate the idiotic so called eco-friendly plastic bottle cup design in the EU more, but that’s a discussion (or post?) for another day.

That said, the “new” Dark Matter series is pretty good, and besides who wouldn’t do absolutely anything and everything for someone like Jennifer Connelly?

The prestigious club of so called men of culture will wholeheartedly and unanimously agree with me on this one. Right? I think so too.

Because I am not a cruel person, I’ll link the trailer of the previous incarnation of Dark Matter for your very own pleasure. Who said that chivalry is dead?

Neuralink Compression Challenge

The Neuralink Compression Challenge kind of took Twitter (yes, I am still calling it that!) by surprise and generated nothing short of a whirlwind.

Achieving 200x lossless compression does seem a bit out there. Okay, not a bit, but a lot. However, when did impossible or ridiculous stop anyone from trying?

I know what you are thinking. Don’t say it, please! I might join in the fun, but considering that I am in a phase where even my side-projects have side-projects, I don’t want to haphazardly just throw yet another one into the pot, because I might need to go buy bigger pot pretty soonish, if I am not careful.

https://mihail.co/2024/05/31/2024-may-retrospective/
2024 April Retrospective
Show full content
2024 April Retrospective

Continuing with the tradition of talking about a fresh or interesting indie games, I’d like to point out Rusty’s Retriment, which came fresh out of the oven at the tail end of the month.

Now, I am not a fan of the so called “cozy game genre”, but this one happens to be the proverbial exception that enforces and solidifies the rule itself, I guess.

Based on the Gamalytic data and the author’s own tweets, it appears that the game has been a commercial success already, mere days since its launch; which is a really thing nice to hear, because it truly is a labor of love.

Give it a try, even if you do not fancy cozy games like myself. Besides, it’s 10% off until the 7th of May.

Rebel Moon

I also ended up watching the second part of Rebel Moon, and it didn’t really change my opinion about how this should have been a series, instead of some long form two part movie-type-thingamajig.

If you were expecting any particular salacious comments, sorry to disappoint you, but you’ll have to look elsewhere on the great information super-highway.

Cursed Makefile Techniques

This is really in the “Kids, do not attempt this at home!” category, but I thought that I’d share it here anyway, just in case someone might stumble upon it and find it useful.

It turns out that it’s possible to craft a Makefile that is compatible with both (GNU) make and Microsoft’s Nmake. Now, why would anyone want such a cursed combination that could accidentally summon the four horsemen of the apocalypse? The answer to that question is for reasons! Very mature, I know.

test.c
#include <stdio.h>

int main(int argc, char *argv[])
{
    printf("Cursed Makefile Techniques ...\n");
    return 0;
}
Makefile
TARGET = test
SOURCE = test.c

# Kids, do not attempt this at home! \
!ifndef __GNU__SLASH__MAKE__ # \
!include mk/nmake.mk # \
!else
include mk/make.mk
# \
!endif
mk/make.mk
# MSYS2 and Cygwin
ifdef COMSPEC
CC := x86_64-w64-mingw32-gcc
TARGET := $(TARGET).exe
endif

all: $(TARGET)

$(TARGET): $(SOURCE)
	$(CC) $(SOURCE) -o $@

clean:
	$(RM) $(TARGET)

.PHONY: all clean
mk/nmake.mk
TARGET = $(TARGET).exe

all: $(TARGET)

$(TARGET): $(SOURCE)
	cl /nologo $(SOURCE) /link /out:$@

clean:
	del /Q $(TARGET)

.PHONY: all clean

This whole “trick” works because make respects the \ character inside comments (lines starting with #) while, nmake does not. It’s really as simple as that.

If you have a relatively small project, it can come in handy; because it allows you to just type make or nmake to build, without having to install additional tools or type in crazy incantations.

Monthly Archive

As per usual, you can find a collection of my random monthly scribblings and musings below.

2024-04-01 / Plush

I know that it’s not much, but it’s honest work.

https://mihail.co/2024/04/30/2024-april-retrospective/
Plush
Show full content
Plush

If you ever used Winamp in the 90s or early 2000s for that matter, then you might have semi-accidentally stumbled upon the mysterious word of Plush.

plush

And, even if you did not for some peculiar reason or another, then you most certainly have seen the “Credits” in the “About Winamp” dialog.

credits

Alright, so what is Plush exactly? Plush is tiny real time software renderer written in the mid-to-late 90s by Justin “time compiler” Frankel of Winamp (Nullsoft) fame.

Demo

I thought to myself that it would be fun little side-project to turn it into a single file library, then compile it down to web assembly via Emscripten and see how it fares in a relatively modern browser.

It’s worth pointing out the fact that I refrained from making any significant changes or optimizations, other than fixing a few compiler warnings, so that everything compiles in a relatively tidy fashion. I hate compiler warnings flooding the screen.

Only something written in C can still compile after being untouched for so many years. This is a fact, of life, and it’s not something up for a debate. Imagine having to compile anything written in C++ that is more than a few years old. Good luck with that. C++ rots at an incredible pace, in fact the more modern, the faster and more accelerated is the rot.

plush

As per usual, many kudos to Suzanne for putting up with me and volunteering as a test subject.

You can check out the web version of the demo above by clicking here.

Source Code

If you are feeling adventurous, or nostalgic, or both at the same time, you can find the full source code and instructions on how to compile the demo over at github.com/icebreaker/plush.

https://mihail.co/2024/04/01/plush/
2024 March Retrospective
Show full content
2024 March Retrospective

Picking this months’ theme was a difficult one for a number of reasons. On one hand, the excellent Shogun mini series has dropped, and in the wee days of the month we got a difficult game about climbing.

A rather though call to make, I am not going to lie. But, if you happen to be reading this in the confines of your browser, then you already know who won as far as the theme is concerned, just by looking at the hero image of this post.

A Difficult Game About Climbing

Before I talk about the game, let me give a little introduction about its maker known as Pontypants.

He released his very first game called Punch A Bunch early last year, which has seen moderate success overall, but one could argue that it did pretty well for a debut title.

bubgamalytic

Now that we’ve seen the numbers, let’s take a look at its trailer below.

Like many indies, he also documented the entire development cycle in the form of devlogs which he uploaded on his YouTube Channel.

After the release of Punch of Bunch, he kind of went silent for a while and it wasn’t clear to me at first as to why. I mean people were speculating as always, and then at the end of last year, he dropped the announcement trailer of what would become a difficult game about climbing.

It was kind of unexpected, but I was happy to see him still kicking and working on something fresh and shiny. The trailer did generate some hype, more than I expected to be honest and I was certain that when he does end up releasing the game, streamers are going to pick it up, just like they did with Getting Over It some years prior.

In many ways, it is the spiritual successor of it and if you look around the reviews on Steam, you’ll notice how people joke that the player has finally gotten out of his pot.

I do feel like it was a missed opportunity to some extent not to do some sort of a launch day cross-promotion or collaboration with Bennett Foddy, but hey nothing is ever perfect.

While there was no extensive series of devlogs this time, Mr. Pontypants has thrown together a launch-day retrospective devlog of sorts, in which he goes over some of the aspects of developing the game, including, extensive and very loud drum rolls, switching engines a while into its development.

The elephant in the room of course is how did the game do? Did it sell enough copies for Mr. Pontypants to continue his indie gamedev journey and will the revenue suffice for him to make yet another game?

Well, according to Gamalytics, it sold extremely well. Obviously, these numbers are estimates and not 100% accurate, but even if we take the most conservative numbers, it’s pretty damn clear that it did well, and just like I suspected when I saw the announcement trailer, streamers and speed runners alike had a field day with it.

adgacgamalytic

Keep in mind that it was self-funded and published, so there will be no cut by a publisher.

In closing, I have a very deep and personal confession to make! Even though games like this are not my pesonal cup of tea, I still bought a copy just to show my support.

xz backdoor

The xz backdoor is a primary example of why monstrosities like systemd should not exist in the first place. Obviously, any piece of software that was linked to the library was affected, but any excuse one can find to bash on systemd should be used to the fullest extent.

I loved the reaction of the Debian Stable community who are literally many many versions behind when it comes to everything rejoicing in unison as there was no way in hell for them to be affected by this under any circumstances.

Will be interesting to follow where this all leads and how the story unfolds as the pieces are put together. At this point in time, there’s just too much speculation to make any educated guesses as to how all this came about.

At any rate, update your systems even if you are not officially affected.

Shogun

I don’t think that is there anything more to be said when it comes Shogun, I’ll just let the trailer speak for itself.

Sadly, it’s just a mini-series which kind of crushed the hopes of people who were thinking that this might be the next Game of Thrones.

I was skeptical of course, but just because I know that it took HBO several attempts, before they managed to pull off Game of Thrones, and it’s easy to forget all the brilliant but half-baked attempts like the excellent Rome that came before and survived only for a couple of seasons.

https://mihail.co/2024/03/31/2024-march-retrospective/
2024 February Retrospective
Show full content
2024 February Retrospective

As February came and went so did the layoffs which seem to be ramping up. According to layoffs.fyi at least 49,386 employees have been laid off since the beginning of the year.

It seems and feels like the layoffs will continue until morale improves.

Night Swim

I feel like it’s a semi-well known fact by now, that I am a horror movie aficionado of some sorts and my first horror movie of the year happened to be titled Night Swim. When it comes to horror flicks, anything produced by James Wan is something that is worth checking out. That’s my general rule of thumb.

Stronghold: Definitive Edition - Swine’s Bay DLC

I mentioned Stronghold Definitive Edition in my traditional 2023 retrospective and here we are a few short months later with a brand new DLC fresh out of the oven.

Running down the memory lane barefoot

The very first time I’ve seen or heard of Stronghold was in the 26th issue (way back from November 2001) of the now defunct XtremPC Magazine.

xpc-26-nov-2001-page-82

If memory serves, the demo was on the companion CD that came with the magazine. Those were the good old days, back when demos actually gave you a pretty good idea of what the full game was about. And, last but not least contained a decent amount of content.

A little rant every now and then is good for the soul

While the original Stronghold did lay down most of the foundation, essentially almost (?) everything that makes “Stronghold be Stronghold” so to speak, only Stronghold Crusader which came out roughly a year or so later manges to achieve actual perfection in the truest sense of the word; which is why many people, including myself still play it and consider it one of the greatest real-time-strategy games every made. Period!

With that said, it feels very odd to me that Firefly Studios would decide to do a definitive edition of the original Stronghold first, rather than going straight to Crusader. Doubly so, considering that Crusader is an evolution of the original code base and it would have made a lot more sense to port it first and then simply retrofit the original. This way, Skirmish mode would have been almost free. As a side note, skirmish mode has been one of the most requested features since the release of the definitive edition.

Last but not least, this approach would have also allowed for a combined definitive (final/collectors?) edition with both titles built from a single unified code base. Oh well!

All this leads me to put my tinfoil hat on and speculate that there must have been some traumatic event around Crusader during its original development, because I really can’t seem to figure out the reasoning behind all this; be it financial, business or otherwise as to why not work on the definitive edition of the most played, requested and highly regarded title first. It’s all just very bizarre to say the least.

I do want to end the topic (and rant!) on a happy and positive note with a review of Stronghold Crusader from the 63rd issue (December 2002) of another now defunct magazine called Level (the Romanian branch).

level-63-dec-2002-page-46

Finally, to drive the proverbial dagger home, considering the fact that I’ve been looking at scans of ancient magazines for the past 2 hours or so, desperately trying to recall and find these reviews, might as well share the review of the original Stronghold, written by the same author no less. It seems only fair and proper.

This was published in the 52nd issue (January 2002) of the magazine.

level-52-jan-2002-page-26

level-52-jan-2002-page-27

level-52-jan-2002-page-28

level-52-jan-2002-page-29

level-52-jan-2002-page-30

Monthly Archive

As per usual, you can find a collection of my random monthly scribblings and musings below.

2024-02-04 / Image Kernels 2024-02-24 / Amalgamation

I know that it’s not much, but it’s honest work.

https://mihail.co/2024/02/29/2024-february-retrospective/
Amalgamation
Show full content
Amalgamation

Amalgamation is not a new thing by any means and in many ways it’s the great-great-great ancestor of what we get to call today single file or header only libraries.

Now, single file libraries don’t necessarily have to be constructed through amalgamation, but many of them are, simply because of ergonomics and ease of development.

There are a few distinct approaches that one can take on the path to achieve amalgamation, namely:

  • simply concatenate N source files into one:
    • cat source1.c source2.c > one.c
  • create a source file acting as an entrypoint, that in turn explicitly #includes all relevant source files and headers; and then run this file through the preprocessor in order to get the final amalgamated result:
    • cc -E one.c -o amalgamated.c
    • cc -O2 amalgamated.c -o output
  • same as above, except one does not preprocess the source file acting as the entrypoint, but rather compiles it directly as-is, skipping the intermediary step:
    • cc -O2 one.c -o output
Lua

Lua for instance takes route number three with the neatly tucked away one.c source file, provided as one of the lesser known extras, not part of the Lua source code tarball.

So, let’s take a look at how this works when it comes to Lua by performing the following incantations:

$ wget https://www.lua.org/ftp/lua-5.4.6.tar.gz
$ tar xzvf lua-5.4.6.tar.gz
$ cd lua-5.4.6/src
$ wget https://www.lua.org/extras/5.4/one.c
$ cc -O2 -DLUA_USE_POSIX -lm one.c -o lua
$ ./lua
Lua 5.4.6  Copyright (C) 1994-2023 Lua.org, PUC-Rio
> print("Hello World")
Hello World

What about building a static library instead? Sure thing!

$ cc -O2 -DLUA_USE_POSIX -DMAKE_LIB -c one.c -o one.o
$ ar rcs liblua.a one.o

Then let’s try it out and see if it’s all in good working order.

$ cat > helloworld.c
#include "lua.h"
#include "lualib.h"
#include "lauxlib.h"
int main(int argc, char *argv[])
{
	lua_State *L = luaL_newstate();
	if(L != NULL)
	{
		luaL_openlibs(L);
		luaL_dostring(L, "print('Hello World')");
		lua_close(L);
	}
	return 0;
}
<CTRL+D>
$ cc -O2 -DLUA_USE_POSIX helloworld.c -L. -llua -lm -o helloworld
$ ldd helloworld
linux-vdso.so.1
libm.so.6 => /usr/lib/libm.so.6
libc.so.6 => /usr/lib/libc.so.6
/lib64/ld-linux-x86-64.so.2 => /usr/lib64/ld-linux-x86-64.so.2
$ ./helloworld
Hello World

Even though I illustrated this on Linux, it works just as beautifully on macOS (OSX) or windows (including with your fan favorite and darling of the great peoples of the interwebs – MSVC).

Ninja

Following in the footsteps of Lua, I wanted to create a similar source file for Ninja, which would allow one to compile it easily and seamlessly without the need for any third-party tools like cmake or python to be present and required. All would one need is a relatively modern (ugh!) C++ compiler.

There was one tiny little problem though. Two of the Ninja headers didn’t seem to have any include guards which made amalgamation impossible.

So, I took mattrs in my own hands and ended up creating a super tiny pull request, that added the missing include guards to the relevant header files. I didn’t expect it to be approved and merged anytime soon to be honest, but surprisingly enough, it was all said and done in a matter of a few days.

With that out of the way, it was now possible to complete my experiment.

$ git clone git@github.com:ninja-build/ninja.git
$ cd ninja/src
$ wget https://mihail.co/extras/ninja/one.cc
$ c++ -O2 one.cc -o ninja
$ ./ninja --version
1.12.0.git

Once my changes end up in an official release of Ninja, it will be possible to just download the official tarball without having to clone the actual Git repository.

And just like that, no more cmake or python needed to bootstrap Ninja from scratch on pretty much all the supported platforms. Pretty damn’ neat, isn’t it? I think so too!

https://mihail.co/2024/02/24/amalgamation/
Image Kernels
Show full content
Image Kernels

I built quite a lot of utilities last year that did image processing in some form or another. However, whenever I’ve written about them, I purposely avoided using the terms kernel and convolution. Why? Simply speaking, to save the unsuspecting AI bros from wetting their pants too much.

So, what are convolution kernels or filters exactly? These fancy words are used to describe an NxN matrix of weights. Is that it? Yes, nothing more and nothing less.

const float kernel[9] = {
  0, 0, 0,
  0, 1, 0,
  0, 0, 0
};

Kernels can be of various sizes, but one of the most common ones that you’ll see in the wild is composed of 9 elements which can be represented by a 3x3 matrix.

This is all useful information, I guess, but how does one use such a kernel? Oh, it’s easy peasy lemon squeezy and boils down to the following steps:

  1. for every pixel in an image
  2. sample the surrounding 8 pixels; the current pixel is in the center
  3. multiply each sampled pixel with the corresponding weight from the kernel
  4. sum up the results

If you are asking yourself if I just wasted 30 seconds of your life to describe doing a dot product in a very backhanded way; then you are absolutely right!

It’s a dot product with two vectors that have 9 elements or components each. Let’s take a look at the code to demystify this further.

const float kernel[9] = {
  0, 0, 0,
  0, 1, 0,
  0, 0, 0
};

const rgba_t samples[9] = { ... };

float sum = 0.0f;

for(int i = 0; i < 9; i++)
	sum += samples[i].r * kernel[i];

Often times, a so called factor and a bias are applied to the sum. More fancy words, huh?

sum = sum * factor + bias;

While optional, these can help fine-tune the results. For instance if one wanted to halve the intensity of the results, one could simply set the factor to 0.5.

Talk is cheap, so let’s take a look at applying a so called emboss kernel and check out the results.

const float emboss_kernel[9] = {
	-2, -1, 0,
	-1,  1, 1,
     0,  1, 2
};

Not too shabby right? But what about colors? Instead of applying the kernel to only one channel, it’s possible to apply it red, green and blue at the same time.

const float kernel[9] = {
	-2, -1, 0,
	-1,  1, 1,
     0,  1, 2
};

const rgba_t samples[9] = { ... };

float r = 0.0f;
float g = 0.0f;
float b = 0.0f;

for(int i = 0; i < 9; i++)
{
	r += samples[i].r * kernel[i];
    g += samples[i].g * kernel[i];
    b += samples[i].b * kernel[i];
}

Before we take a look at the full source code, let’s awe a gander at another shiny kernel that results in a rough outline.

const float outline_kernel[9] = {
	-1, -1, -1,
	-1,  8, -1,
    -1, -1, -1
};

In order to spice things up a little bit even more, let’s also set the factor to a value of 2 this time.

Source Code

You can find the full source code below as per usual.

/*
	MIT LICENSE
	Copyright (c) 2024, Mihail Szabolcs
*/
#include <stdbool.h>
#include <stdint.h>
#include <stdlib.h>
#include <stdio.h>
#include <memory.h>
#include <math.h>

#define STBI_NO_HDR
#define STBI_NO_LINEAR
#define STB_IMAGE_IMPLEMENTATION
#define STB_IMAGE_WRITE_IMPLEMENTATION
#include "stb_image.h"
#include "stb_image_write.h"

#ifndef UNUSED
	#define UNUSED(x) (void)(x)
#endif

#ifndef BORDER_SIZE
	#define BORDER_SIZE 1
#endif

#ifndef KERNEL_SIZE
	#define KERNEL_SIZE 9
#endif

#define clampf(a, min, max) fmax(min, fmin(max, a))
#define grayscale(c) (((((c).r << 1) + (c).r) + ((c).g << 2) + (c).b) >> 3)

typedef union
{
	uint32_t value;
	struct
	{
		uint32_t r : 8;
		uint32_t g : 8;
		uint32_t b : 8;
		uint32_t a : 8;
	};
} rgba_t;

static void convert(
	rgba_t *image,
	const int w,
	const int h
);

static void copy(
	const rgba_t *restrict input,
	rgba_t *restrict output,
	const int iw,
	const int ih,
	const int ow,
	const int oh
);

static void process(
	const rgba_t *restrict input,
	rgba_t *restrict output,
	const int w,
	const int h,
	const float kernel[KERNEL_SIZE],
	const float factor,
	const float bias
);

int main(int argc, char *argv[])
{
	int w, h, ibw, ibh, bpp, ret;
	float factor, bias, kernel[KERNEL_SIZE] = { 0, 0, 0, 0, 1, 0, 0, 0, 0 };
	bool grayscale;
	rgba_t *input, *input_border, *output;

	if(argc < 3)
	{
		fprintf(
            stderr,
            "usage: %s input.png output.png [arguments]\n\n",
            argv[0]
        );
		fprintf(stderr, "arguments:\n");
		fprintf(stderr, "--kernel    - specify kernel matrix\n");
		fprintf(stderr, "--factor    - specify kernel factor\n");
		fprintf(stderr, "--bias      - specify kernel bias\n");
		fprintf(stderr, "--grayscale - convert input image to grayscale\n");
		return EXIT_FAILURE;
	}

	grayscale = false;
	factor = 1.0f;
	bias = 0.0f;

	for(int i = 3; i < argc; i++)
	{
		if(!strcmp(argv[i], "--kernel") && i + 1 < argc)
		{
			ret = sscanf(
				argv[++i],
				"(%f,%f,%f,%f,%f,%f,%f,%f,%f)",
				&kernel[0],
				&kernel[1],
				&kernel[2],
				&kernel[3],
				&kernel[4],
				&kernel[5],
				&kernel[6],
				&kernel[7],
				&kernel[8]
			);
			if(ret != KERNEL_SIZE)
			{
				fprintf(
                    stderr,
                    "error: --kernel is invalid\n"
                );
				return EXIT_FAILURE;
			}
		}
		else if(!strcmp(argv[i], "--factor") && i + 1 < argc)
		{
			factor = atof(argv[++i]);
		}
		else if(!strcmp(argv[i], "--bias") && i + 1 < argc)
		{
			bias = atof(argv[++i]);
		}
		else if(!strcmp(argv[i], "--grayscale"))
		{
			grayscale = true;
		}
		else
		{
			fprintf(
                stderr,
                "error: invalid argument '%s' or argument value\n",
                argv[i]
            );
			return EXIT_FAILURE;
		}
	}

	input = (rgba_t *) stbi_load(argv[1], &w, &h, &bpp, sizeof(rgba_t));
	if(input == NULL)
	{
		fprintf(stderr, "failed to load input: %s\n", stbi_failure_reason());
		return EXIT_FAILURE;
	}

	ibw = w + (BORDER_SIZE << 1);
	ibh = h + (BORDER_SIZE << 1);

	input_border = malloc(ibw * ibh * sizeof(rgba_t));
	if(input_border == NULL)
	{
		fprintf(
            stderr,
            "failed to allocate memory for input image with border\n"
        );
		stbi_image_free(input);
		return EXIT_FAILURE;
	}

	output = malloc(ibw * ibh * sizeof(rgba_t));
	if(output == NULL)
	{
		fprintf(stderr, "failed to allocate memory for output image\n");
		free(input_border);
		stbi_image_free(input);
		return EXIT_FAILURE;
	}

	if(grayscale)
	{
		convert(input, w, h);
		fprintf(stdout, "converted input image to grayscale ...\n");
	}

	copy(input, input_border, w, h, ibw, ibh);

	stbi_image_free(input);
	input = input_border;

	process(input, output, ibw, ibh, kernel, factor, bias);

	ret = stbi_write_png(
		argv[2],
		w,
		h,
		sizeof(rgba_t),
		output + BORDER_SIZE + (BORDER_SIZE * ibw),
		ibw * sizeof(rgba_t)
	);
	if(ret == 0)
	{
		fprintf(stderr, "failed to write output image: '%s'\n", argv[2]);
		ret = EXIT_FAILURE;
	}
	else
	{
		fprintf(stdout, "wrote output image to '%s' ...\n", argv[2]);
		ret = EXIT_SUCCESS;
	}

	free(output);
	free(input);
	return ret;
}

static void convert(
	rgba_t *image,
	const int w,
	const int h
)
{
	uint32_t a;
	rgba_t c;

	for(int i = 0, s = w * h; i < s; i++)
	{
		c = image[i];

		a = grayscale(c);
		c.r = a;
		c.g = a;
		c.b = a;

		image[i] = c;
	}
}

static void copy(
	const rgba_t *restrict input,
	rgba_t *restrict output,
	const int iw,
	const int ih,
	const int ow,
	const int oh
)
{
	memset(output, 0, ow * oh * sizeof(rgba_t));

	output = output + BORDER_SIZE + ow * BORDER_SIZE;

	for(int y = 0; y < ih; y++)
	{
		memcpy(output, input, iw * sizeof(rgba_t));
		input += iw;
		output += ow;
	}
}

static void process(
	const rgba_t *restrict input,
	rgba_t *restrict output,
	const int w,
	const int h,
	const float kernel[KERNEL_SIZE],
	const float factor,
	const float bias
)
{
	float r, g, b, k;
	rgba_t s, samples[KERNEL_SIZE];

	for(int y = BORDER_SIZE; y < h - BORDER_SIZE; y++)
	{
		const int yo  = y * w;
		const int you = (y - 1) * w;
		const int yod = (y + 1) * w;

		for(int x = BORDER_SIZE; x < w - BORDER_SIZE; x++)
		{
			const int xl = x - 1;
			const int xr = x + 1;

			samples[0] = input[xl + you]; // -1, -1
			samples[1] = input[ x + you]; //  0, -1
			samples[2] = input[xr + you]; //  1, -1
			samples[3] = input[xl +  yo]; // -1,  0
			samples[4] = input[ x +  yo]; //  0,  0
			samples[5] = input[xr +  yo]; //  1,  0
			samples[6] = input[xl + yod]; // -1,  1
			samples[7] = input[ x + yod]; //  0,  1
			samples[8] = input[xr + yod]; //  1,  1

			r = 0.0f;
			g = 0.0f;
			b = 0.0f;

			for(int i = 0; i < KERNEL_SIZE; i++)
			{
				s = samples[i];
				k = kernel[i];

				r += s.r * k;
				g += s.g * k;
				b += s.b * k;
			}

			r = r * factor + bias;
			g = g * factor + bias;
			b = b * factor + bias;

			r = clampf(r, 0, 255);
			g = clampf(g, 0, 255);
			b = clampf(b, 0, 255);

			output[x + yo] = (rgba_t) {
				.r = r,
				.g = g,
				.b = b,
				.a = 0xFF
			};
		}
	}
}

/* vim: set ts=4 sw=4 sts=4 noet: */

I am happy to report that no Skeksis has been harmed during the writing of this blog post. Hopefully this little disclaimer puts your mind at ease. This is a safe and very inclusive place.

https://mihail.co/2024/02/04/image-kernels/
2024 January Retrospective
Show full content
2024 January Retrospective

January started out pretty much as bad as I expected that it would. With mass layoffs at several companies, which pretty much reinforces my intuition from last year insofar that the actual financial crisis hasn’t even started yet in earnest; and when it finally does, it will make the one from way back 2007-2008 look like a nice dreamy-holiday.

It’s not all doom and gloom of course. Things are never truly black and white, there’s always a lot of shades of gray in between. Just like when one walks around in an old ex-communist neighborhood anywhere in the so called Eastern Bloc.

In other news, I intend to continue scribbling even more than last year. I have a fair few interesting topics lined up already. Some of them, I just never gotten to way back in December. So stay tuned.

once.com

37signals announced once.com sometime at the tail end of last year if I am not mistaken. The announcement itself was kind of vague, but as the launch date of the so called first once product came closer, it became clear what was the proverbial fuss all about.

The buy once and run on your own server model is definitely not a revolutionary concept by any stretch of the imagination, but with the current state of the market it might just work better than usual.

One interesting tidbit that caught my attention when it comes to all this is the fact of using sqlite as a database for a non-desktop/local-app in so called production mode. And, to make this even more interesting, in a chat-app no less, where there will be a lot of concurrent writes.

Truth be told I never put sqlite to any sort of a concurrent-writes benchmark or test, but now it’s definitely an item on my ever growing backlog of things to play around with.

American Nightmare

The only interesting thing I ended up watching this month was the American Nightmare mini-series.

https://mihail.co/2024/01/31/2024-january-retrospective/
2023 Retrospective
Show full content
2023 Retrospective

Here we are on day 1401 of 2020 and it’s time to start things off with the quintessential and very controversial yearly GitHub contributions or commits chart.

2023

Not good and not terrible to use the words of a wise man located more to the east from my location.

But on a more serious note, 2023 has been a pretty rough year all around no matter how one slices or dices it, and I feel like 2024 will continue to be the same.

I didn’t expect to have to face a financial crisis twice in a row in my natural lifetime, yet here we are. And, this one is just getting started! By time it’s all said and done, 2007 - 2008 will look like a nice and pleasant holiday with sunny and happy days.

It’s not all doom and gloom of course and I am most definitely not the one to beat the drums of the Apocalypse and trying to summon the horsemen by any means. On the contrary, I am just being realistic and preparing for the worst. Better be prepared than show the other cheek, that’s my motto.

Archive

Just like last year, I continued writing monthly retrospectives at the tail end of each month, which you can find conveniently linked below. Just in case you have nothing better to do with your precious time.

2023-01-01 / FreeDOS and Floppy Bird 2023-01-31 / 2023 January Retrospective 2023-02-07 / Creating normal maps from diffuse maps 2023-02-28 / 2023 February Retrospective 2023-03-31 / 2023 March Retrospective 2023-04-29 / Universal Scene Description 2023-04-30 / 2023 April Retrospective 2023-05-31 / 2023 May Retrospective 2023-06-25 / Branchless Line Drawing 2023-06-30 / 2023 June Retrospective 2023-07-08 / Anaglyphs 2023-07-10 / Image Diff(erence) Viewer 2023-07-31 / 2023 July Retrospective 2023-08-31 / 2023 August Retrospective 2023-09-30 / 2023 September Retrospective 2023-10-31 / 2023 October Retrospective 2023-11-30 / 2023 November Retrospective 2023-12-05 / Upscaling Pixel Art 2023-12-06 / Generating Voronoi Noise 2023-12-17 / Generating Noise 2023-12-22 / The VLC Xmas Hat Easter Egg 2023-12-31 / 2023 Retrospective The Legend of Zelda: Tears of the Kingdom

It should go without saying that my game of the year is undoubtedly Tears of the Kingdom. Which shouldn’t really shock anyone who happens to be a reader of this blog, as I posted about it quite heavily earlier in the year up to its official release.

I put around 85 hours or so into it, and I barely managed to scratch the surface.

totkt

Didn’t even start any of the main quests yet. Now, I know that many people just flew through it and called it a day, but for me a game like this is more like a spiritual experience, which you don’t just skim through.

Will be picking it up and enjoying it every now and then for at least a few hundred hours more, just like I did with Breath of the Wild back in the day.

Here’s a collage of footage I took during my exploits earlier in the year. No major spoilers, I promise.

Vampire Survivors Clones

I didn’t join in the absolute craze when Vampire Survivors first launched and won every single award possible out there. It’s just not in my style to jump head in, I like to let the dust settle a little bit before taking a look.

As expected, by the time I looked there were several clones laying around, which of course I had to check out as a good citizen of the information super highway.

Here’s a list of the clones or survivor-likes or bullet-heavens thatI spent various degrees of time with during the past couple of months. As per usual it will take a while before the great sages of the industry can collectively agree on the genre; until then, personally, I will just call them clones, just like we collectively used to call all first person shooters DOOM-clones in the early 90s.

Spirit Hunters: Infinite Horde

You can find it on Steam by clicking here.

Soulstone Survivors

You can find it on Steam by clicking here.

Death Must Die

You can find it on Steam by clicking here.

Halls of Tornment

You can find it on Steam by clicking here.

Army of Ruin

You can find it on Steam by clicking here.

Yet Another Zombie Survivors

You can find it on Steam by clicking here.

Nebula

You can find it on Steam by clicking here.

Insect Swarm

You can find it on Steam by clicking here.

Tiny Rogues

While not an actual survivors-clone in the strict sense of the word, I still felt like including it in the list.

You can find it on Steam by clicking here.

Verdict

Out of the bunch that I tried and listed above, Halls of Tornment is the one that got it pretty close and managed to balance things out, where everything felt fair and you wanted to try again, rather than feeling frustrated and making the entire game feel like a chore.

Balancing is no easy feat in any game that has RPG or RPG-adjent elements. Nerf too little or too much and you can end up frustrating the players, which some of the contenders found out the hard way; if you are brave enough to look at the “Community” sections on their respective Steam pages.

Stronghold: Definitive Edition

When the Definitive Edition was announced and then released it was one of the most unexpected things that have transpired this past year.

It just wasn’t something that anyone expected, I certainly didn’t. Now, let’s just hope that it sold well enough to recoup the costs and then some; and, with some luck we’ll see a definitive edition of Stronghold Crusader as well, which I am still playing to this day.

Based on the estimates by Gamalytic, it doesn’t look too shabby, I must say.

shdeg

2024 .plan

While I usually try to stay away from new years resolutions as much as humanly possible for a person of my age and position; I thought that I’d throw together a few bullet points with things that I’d like to get to and/or complete in the next year to come.

  • Write more (posts, etc.); I hinted at this already a couple of times both in this post and in others
  • Try out streaming some dev-ing on X
  • Release an experimental version of the portable build system that I’ve been talking about every now and then on here
  • Release another boot-able / M$-DOS game (very likely a clone of some sorts of Suika Game)
  • Get the ball rolling more “seriously” on some of my other bigger side projects (which ones you might be tempted to ask? well, that’s to be determined!)
  • Make my first million green dollars!

Until next year, as server said: END OF LINE!

https://mihail.co/2023/12/31/2023-restrospective/
The VLC Xmas Hat Easter Egg
Show full content
The VLC Xmas Hat Easter Egg

As a certified easter egg hunter, I pride myself with discovering the llama snake inside the modern Bento Skin of Winamp 5, all the way back in late 2007 or thereabouts. While I can’t be 100% certain that I was the first person that ended up discovering it, the chances are pretty damn high. I do remember searching around the information super-highway at the time and finding nothing about it. My google-fu was always top notch. Trust me on this!

Now, the so called Xmas hat easter egg in VLC is more up in your face so to speak. There’s no special key combination to hold or special icons to click in some obscure About dialog box or a place no user dares to wander.

In other words, I am sure you noticed how the VLC icon changes to one with a tiny Xmas hat. This happens at a certain point during the month of December and I could never put my finger on the exact day it happens.

As one might expect, not everybody was super thrilled about this transpiring as you can see for yourself in this beautiful thread over the official VLC forums.

So, I thought that it’s about high time to take a look at the source code and figure it all out. The benefits of VLC being an open source project is that I don’t even have to whip out my reverse engineering toolkit and disassemble the binary. It’s all out there in the open for all to see. Isn’t open source wonderful? I think so to.

If we awe a gander inside modules/gui/qt/qt.cpp, we can locate the following block of code.

if(QDate::currentDate().dayOfYear() >= QT_XMAS_JOKE_DAY &&
	var_InheritBool(p_intf, "qt-icon-change"))
{
	app.setWindowIcon(
		QIcon::fromTheme(
			"vlc-xmas",
			QIcon(":/logo/vlc128-xmas.png")
		)
	);
}
else
{
	app.setWindowIcon(
		QIcon::fromTheme(
			"vlc",
			QIcon(":/logo/vlc256.png")
		)
	);
}

The elephant in the room is value of QT_XMAS_JOKE_DAY, which of course we can find conveniently defined in modules/gui/qt/qt.hpp as one would expect.

/*
	After this day of the year, the usual VLC cone is replaced by another cone
	wearing a Father Xmas hat.

	Note this icon doesn't represent an endorsment of Coca-Cola company.
*/
#define QT_XMAS_JOKE_DAY 354

It’s all revealed. In other words if the current day of the year is greater or equal to 354, the Xmas specific icon is used.

There are a couple of other spots where there is a similar conditional check, like setting up the system tray icon for instance.

Yet another curious Xmas related thingamajig that I stumbled upon in the source code while taking a look is some animated snowflake effect.

Spotted this naughty boy inside modules/gui/qt/components/interface_widgets.cpp while looking for instances where QT_XMAS_JOKE_DAY was being referenced.

if(QDate::currentDate().dayOfYear() >= QT_XMAS_JOKE_DAY
    && var_InheritBool( p_intf, "qt-icon-change"))
{
	bgWidget = new EasterEggBackgroundWidget(p_intf);
	CONNECT(this, kc_pressed(), bgWidget, animate());                          }
else
{
	bgWidget = new BackgroundWidget(p_intf);
}

By looking at the code we can see that this is activated by a signal called kc_pressed(), which is hooked up to the animate() slot of the EasterEggBackgroundWidget.

Don’t get too discouraged by all this Qt specific techno-prep-school-palabra. These are just rather fancy words for events and event handlers, nothing less and nothing more.

The next step was to track down where the kc_pressed() signal was being emitted from, which we can find inside modules/gui/qt/main_interface.cpp, conveniently tucked away in the key press event handler.

void MainInterface::keyPressEvent(QKeyEvent *e)
{
    handleKeyPress(e);

    /* easter eggs sequence handling */
    if(e->key() == kc[i_kc_offset])
        i_kc_offset++;
    else
        i_kc_offset = 0;

    if(i_kc_offset == (sizeof(kc) / sizeof(Qt::Key)))
    {
        i_kc_offset = 0;
        emit kc_pressed();
    }
}

It looks like it’s tracking a sequence of key presses, and if they all match, then finally the kc_pressed() signal is emitted and the sequence is reset.

The kc static array containing the key codes is defined within the same file and it looks like this.

const Qt::Key MainInterface::kc[10] =
{
    Qt::Key_Up, Qt::Key_Up,
    Qt::Key_Down, Qt::Key_Down,
    Qt::Key_Left, Qt::Key_Right, Qt::Key_Left, Qt::Key_Right,
    Qt::Key_B, Qt::Key_A
};

Do you recognize the sequence? It’s the infamous sequence known as the Konami code.

vlcsnow

Is there anything else? Glad you asked. There is one more easter egg, which is not Xmas related, but just as recognizable, perhaps even more so.

If we take another careful look inside modules/gui/qt/components/interface_widgets.cpp, we stumble upon this piece of pure concentrated goodness on steroids or Dr. Pepper, whichever floats your boat, there are no judgments being passed around here, as this is a consecrated safe place.

void BackgroundWidget::titleUpdated(const QString& title)
{
    /* don't ask */
    if(var_InheritBool(p_intf, "qt-icon-change") && !title.isEmpty())
    {
        int i_pos = title.indexOf(
            "Ki" /* Bps */ "ll",
            0,
            Qt::CaseInsensitive
        );
        if(i_pos != -1 &&
           i_pos + 5 == title.indexOf("Bi" /* directional */ "ll",
                                      i_pos, Qt::CaseInsensitive))
                updateDefaultArt(":/logo/vlc128-kb.png");
        else if(QDate::currentDate().dayOfYear() >= QT_XMAS_JOKE_DAY)
				updateDefaultArt( ":/logo/vlc128-xmas.png" );
        else
				updateDefaultArt( ":/logo/vlc128.png" );
    }
}

This piece of code simply checks if the title of the currently playing media starts with Kill Bill and if it does then the default background art is changed to a Kill Bill themed one, which you can see in all its glory below.

vlckillbill

It’s important to note that this only takes effect if the media doesn’t have its own background art embedded within it, and the easiest way to trigger the titleUpdated event is skip to a few seconds before the end of the media and then let it finish.

That is all. I hope this was a fun little pre-Xmas excursion in the world of easter eggs, which by the way are harder and harder to come by in our day and age. So sad, I know!

And, kudos to all the contributors and volunteers keeping VLC alive and kicking. Truly a thankless job!

https://mihail.co/2023/12/22/vlc-xmas-hat-easter-egg/
Generating Noise
Show full content
Generating Noise

In my previous post I talked about generating voronoi noise, but when the subject of noise comes up, most people tend to think of perlin noise or simplex noise rather than anything else.

However, one doesn’t have to go that fancy to get relatively decent results. But, without any further ado, let’s dive into it and explore the world of noise.

To start things off on the right foot, let’s generate some good old static noise.

static void process(
    rgba_t *restrict output,
    const int w,
    const int h
)
{
    int i, n, c;

    for(i = 0, n = w * h; i < n; i++)
    {
        c = rand() & 0xFF;
        output[i] = (rgba_t) {
            .r = c,
            .g = c,
            .b = c,
            .a = 0xFF
        };
    }
}

noise

Do not worry, there’s no poltergeist coming through if you stare at it for too long. I pinky promise.

Let’s see what happens if we blur this by simply taking the average of the surrounding pixels.

static void randomize(
    rgba_t *restrict output,
    const int w,
    const int h,
    const unsigned int seed
)
{
    int i, n, c;

    srand(seed);

    for(i = 0, n = w * h; i < n; i++)
    {
        c = rand() & 0xFF;
        output[i] = (rgba_t) {
            .r = c,
            .g = c,
            .b = c,
            .a = 0xFF
        };
    }
}

static void process(
    rgba_t *restrict output,
    const int w,
    const int h
)
{
    int x, y, yo, you, yod, ww, hh;
    uint32_t c;
    rgba_t cu, cd, cl, cr;

    ww = w - 1;
    hh = h - 1;

    for(y = 0; y < h; y++)
    {
        yo  = y * w;
        you = max(y - 1,  0) * w;
        yod = min(y + 1, hh) * w;

        for(x = 0; x < w; x++)
        {
            cu = output[x + you];
            cd = output[x + yod];
            cl = output[max(x - 1,  0) + yo];
            cr = output[min(x + 1, ww) + yo];

            c = (cu.r + cd.r + cl.r + cr.r) >> 2;

            output[x + yo] = (rgba_t) {
                .r = c,
                .g = c,
                .b = c,
                .a = 0xFF
            };
        }
    }
}

noiseb

This works best with a relatively small size and at least 4 passes of blur.

w = 128;
h = 128;

randomize(output, w, h, seed);

for(i = 0; i < 4; i++)
	process(output, w, h);

Now that is not too shabby, but we can do better than that, but first we’ll divorce the randomization from the output image and fill out a separate array with values in the range of [0, 255].

static void randomize(
    uint8_t *restrict noise,
    const int w,
    const int h,
    const unsigned int seed
)
{
    int i, n;

    srand(seed);

    for(i = 0, n = w * h; i < n; i++)
        noise[i] = rand() & 0xFF;
}

Then we modify the processing portion to sample N pixels around the current pixel in the range of [-N/2, +N/2].

static void process(
	const uint8_t *restrict noise,
	rgba_t *restrict output,
	const int w,
	const int h,
	const int num_samples
)
{
	int i, x, y, xx, yy, yo, ww, hh, num_samples;
	uint32_t c, cc;
	float num_samples;

	ww = w - 1;
	hh = h - 1;

	half_num_samples = num_samples >> 1;
	inv_num_samples = 1.0f / (num_samples << 1);

	for(y = 0; y < h; y++)
	{
		yo = y * w;

		for(x = 0; x < w; x++)
		{
			cc = 0;

			for(i = -half_num_samples; i <= half_num_samples; i++)
			{
				xx = clamp(x + i, 0, ww);
				yy = clamp(y + i, 0, hh);

				cc += noise[xx +  y * w];
				cc += noise[x  + yy * w];
			}

			c = cc * inv_num_samples;

			output[x + yo] = (rgba_t) {
				.r = c,
				.g = c,
				.b = c,
				.a = 0xFF
			};
		}
	}
}

noise

Not bad, not terrible! But, sadly it’s not the noise we are looking for.

Let’s combine the two techniques into one and see if we can get closer to the kind of noise that we are looking for. Namely, something more akin to what one gets with those “noise cloud” type filters in amazing programs like photoshop or gimp. I am sure that you know what I am talking about.

#define NOISE_MAX (1 << 14)
#define NOISE_MAX_MASK (NOISE_MAX - 1)
#define NOISE_MAX_MASK_INV (1.0f / (float) NOISE_MAX_MASK)

static void randomize(
    float *restrict noise,
    const int w,
    const int h,
    const unsigned int seed
)
{
    int i, n;

    srand(seed);

    for(i = 0, n = w * h; i < n; i++)
        noise[i] = (rand() & NOISE_MAX_MASK) * NOISE_MAX_MASK_INV;
}

#define fract(a) ((a) - ((int)(a)))
#define lerp(a, b, t) ((1 - (t)) * (a) + (t) * (b))

static void process(
	const float *restrict noise,
	rgba_t *restrict output,
	const int w,
	const int h,
	const int num_samples
)
{
	int x, y, i, xx0, yy0, xx1, yy1, yyo0, yyo1, yo, ww, hh;
	float inv_num_samples, inv_scale, xx, yy, fx, fy;
    float ac, v0, v1, c, c0, c1, c2, c3;

	ww = w - 1;
	hh = h - 1;

	inv_num_samples = 127.0f / (float) num_samples;

	for(y = 0; y < h; y++)
	{
		yo = y * w;

		for(x = 0; x < w; x++)
		{
			ac = 0;

			for(i = 1; i <= num_samples; i <<= 1)
			{
                inv_scale = 1.0f / (float) i;

				xx = x * inv_scale;
				yy = y * inv_scale;

				xx0 = ((int) xx) & ww;
				yy0 = ((int) yy) & hh;

				xx1 = (xx0 + 1) & ww;
				yy1 = (yy0 + 1) & hh;

				yyo0 = yy0 * w;
				yyo1 = yy1 * w;

				c0 = noise[xx0 + yyo0];
				c1 = noise[xx1 + yyo0];
				c2 = noise[xx0 + yyo1];
				c3 = noise[xx1 + yyo1];

				fx = fract(xx);
				fy = fract(yy);

				v0 = lerp(c0, c1, fx);
				v1 = lerp(c2, c3, fx);

				ac += lerp(v0, v1, fy) * i;
			}

			c = ac * inv_num_samples;

			output[x + yo] = (rgba_t) {
				.r = c,
				.g = c,
				.b = c,
				.a = 0xFF
			};
		}
	}
}

First, we changed the randomize function to generate random floating point values in the range of [0,1] in order to increase accuracy and simplify things down the line.

Second, we now sample from [1, num_samples] in increments of powers of two. In other words, if num_samples = 128, then we end up sampling [1, 2, 4, 8, 16, 32, 64, 128].

Third, we scale the x and y coordinates by the current sample size. We could avoid the lonely division there, but it’s not end of the world since everything else is using multiplications and additions.

I am talking about this portion of the code of course, in case you’ve been scratching your head.

inv_scale = 1.0f / (float) i;

xx = x * inv_scale;
yy = y * inv_scale;

Forth, instead of simply taking the mean average of the surrounding pixels we perform bilinear interpolation, which is just a fancy word for linearly interpolating on the x and y axis at the same time.

Let’s take a deeper dive into this because this is really the most interesting part and it’s also where the so called magic happens.

xx0 = ((int) xx) & ww;
yy0 = ((int) yy) & hh;

xx1 = (xx0 + 1) & ww;
yy1 = (yy0 + 1) & hh;

yyo0 = yy0 * w;
yyo1 = yy1 * w;

c0 = noise[xx0 + yyo0];
c1 = noise[xx1 + yyo0];
c2 = noise[xx0 + yyo1];
c3 = noise[xx1 + yyo1];

fx = fract(xx);
fy = fract(yy);

v0 = lerp(c0, c1, fx);
v1 = lerp(c2, c3, fx);

ac += lerp(v0, v1, fy) * i;

I know that it looks a bit intimidating, but it’s simpler than it looks as we’ll see in a second and basically it boils down to the following operations:

  1. clamp x0 and y0
  2. offset x0 and y0 by +1 and clamp each
  3. sample the noise at the following coordinates:
    1. [x0, y0]
    2. [x1, y0]
    3. [x0, y1]
    4. [x1, y1]
  4. interpolate [x0, y0] and [x1, y0] by using t = fract(xx)
  5. interpolate [x0, y1] and [x1, y1] by using t = fract(xx)
  6. interpolate the result of the two previous interpolations by using t = fract(yy)
  7. scale the result of the interpolation by the current sample size; lerp * i
  8. accumulate the result into ac

Last by not least, we normalize the accumulated result and bring it into the [0, 127] range.

c = ac * inv_num_samples;

If we expand this, we get the expression seen below; which of course has an unnecessary division.

c = 127 * ac / num_samples;

That’s all. Anyway, let’s see what we get if we execute this by using the following values.

variable value w 1024 h 1024 num_samples 128 seed 4213666

noisebi

Voilà! Not as nice as good old perlin noise or its friends, but it’s something that one can throw together without too much fuss in a couple of minutes and it’s reasonably cheap in terms of speed.

Benchmark
$ time noise noise.png 1024 128 4213666
wrote output image to 'noise.png' ...

real    0m0.279s
user    0m0.268s
sys     0m0.010s

$ time noise noise.png 1024 128 4213666
wrote output image to 'noise.png' ...

real    0m0.271s
user    0m0.263s
sys     0m0.007s

$ time noise noise.png 1024 128 4213666
wrote output image to 'noise.png' ...

real    0m0.271s
user    0m0.266s
sys     0m0.005s
Source Code

You can find the full source code below as per usual.

/*
    MIT LICENSE
    Copyright (c) 2023, Mihail Szabolcs
*/
#include <stdbool.h>
#include <stdint.h>
#include <stdlib.h>
#include <stdio.h>
#include <time.h>

#define STB_IMAGE_WRITE_IMPLEMENTATION
#include "stb_image_write.h"

#ifndef UNUSED
	#define UNUSED(x) (void)(x)
#endif

#ifndef DEFAULT_SIZE
	#define DEFAULT_SIZE 1024
#endif

#ifndef DEFAULT_NUM_SAMPLES
	#define DEFAULT_NUM_SAMPLES 128
#endif

#define NOISE_MAX (1 << 14)
#define NOISE_MAX_MASK (NOISE_MAX - 1)
#define NOISE_MAX_MASK_INV (1.0f / (float) NOISE_MAX_MASK)

#define min(a, b) ((a) < (b) ? (a) : (b))
#define max(a, b) ((a) > (b) ? (a) : (b))
#define clamp(x, a, b) max(a, min(b, x))
#define fract(a) ((a) - ((int)(a)))
#define lerp(a, b, t) ((1 - (t)) * (a) + (t) * (b))

typedef union
{
	uint32_t value;
	struct
	{
		uint32_t r : 8;
		uint32_t g : 8;
		uint32_t b : 8;
		uint32_t a : 8;
	};
} rgba_t;

static void randomize(
	float *restrict output,
	const int w,
	const int h,
	const unsigned int seed
);

static void process(
	const float *restrict noise,
	rgba_t *restrict output,
	const int w,
	const int h,
	const int num_samples
);

int main(int argc, char *argv[])
{
	int w, h, num_samples, ret;
	unsigned int seed;
	float *noise;
	rgba_t *output;

	if(argc < 2)
	{
		fprintf(
            stderr,
            "usage: %s output.png [[size] [num_samples] [seed]]\n",
            argv[0]
        );
		return EXIT_FAILURE;
	}

	if(argc > 2)
	{
		w = clamp(atoi(argv[2]), 16, 8192);
		h = w;
	}
	else
	{
		w = DEFAULT_SIZE;
		h = DEFAULT_SIZE;
	}

	if(argc > 3)
		num_samples = clamp(atoi(argv[3]), 1, 4096);
	else
		num_samples = DEFAULT_NUM_SAMPLES;

	if(argc > 4)
		seed = atoi(argv[4]);
	else
		seed = time(NULL);

	noise = malloc(w * h * sizeof(float));
	if(noise == NULL)
	{
		fprintf(stderr, "failed to allocate memory for output image\n");
		return EXIT_FAILURE;
	}

	output = malloc(w * h * sizeof(rgba_t));
	if(output == NULL)
	{
		free(noise);
		fprintf(stderr, "failed to allocate memory for output image\n");
		return EXIT_FAILURE;
	}

	randomize(noise, w, h, seed);
	process(noise, output, w, h, num_samples);

	ret = stbi_write_png(
        argv[1],
        w,
        h,
        sizeof(rgba_t),
        output,
        w * sizeof(rgba_t)
    );
	if(ret == 0)
	{
		fprintf(stderr, "failed to write output image: '%s'\n", argv[1]);
		ret = EXIT_FAILURE;
	}
	else
	{
		fprintf(stdout, "wrote output image to '%s' ...\n", argv[1]);
		ret = EXIT_SUCCESS;
	}

	free(output);
	free(noise);
	return ret;
}

static void randomize(
	float *restrict noise,
	const int w,
	const int h,
	const unsigned int seed
)
{
	int i, n;

	srand(seed);

	for(i = 0, n = w * h; i < n; i++)
		noise[i] = (rand() & NOISE_MAX_MASK) * NOISE_MAX_MASK_INV;
}

static void process(
	const float *restrict noise,
	rgba_t *restrict output,
	const int w,
	const int h,
	const int num_samples
)
{
	int x, y, i, xx0, yy0, xx1, yy1, yyo0, yyo1, yo, ww, hh;
	float inv_num_samples, inv_scale, xx, yy, fx, fy;
    float ac, v0, v1, c, c0, c1, c2, c3;

	ww = w - 1;
	hh = h - 1;

	inv_num_samples = 127.0f / (float) num_samples;

	for(y = 0; y < h; y++)
	{
		yo = y * w;

		for(x = 0; x < w; x++)
		{
			ac = 0;

			for(i = 1; i <= num_samples; i <<= 1)
			{
				inv_scale = 1.0f / (float) i;

				xx = x * inv_scale;
				yy = y * inv_scale;

				xx0 = ((int) xx) & ww;
				yy0 = ((int) yy) & hh;

				xx1 = (xx0 + 1) & ww;
				yy1 = (yy0 + 1) & hh;

				yyo0 = yy0 * w;
				yyo1 = yy1 * w;

				c0 = noise[xx0 + yyo0];
				c1 = noise[xx1 + yyo0];
				c2 = noise[xx0 + yyo1];
				c3 = noise[xx1 + yyo1];

				fx = fract(xx);
				fy = fract(yy);

				v0 = lerp(c0, c1, fx);
				v1 = lerp(c2, c3, fx);

				ac += lerp(v0, v1, fy) * i;
			}

			c = ac * inv_num_samples;

			output[x + yo] = (rgba_t) {
				.r = c,
				.g = c,
				.b = c,
				.a = 0xFF
			};
		}
	}
}

/* vim: set ts=4 sw=4 sts=4 noet: */
https://mihail.co/2023/12/17/generating-noise/
Generating Voronoi Noise
Show full content
Generating Voronoi Noise

Voronoi Diagrams always seemed to me like a weird stepchild of noise and signed distance fields.

The process of creating a Voronoi diagram is not terribly complicated and it consists of two steps:

  1. Pick N random points and colors
  2. For each pixel find the closest point relative to it and use its color
  3. Profit!

Talk is cheap, so let’s take a look at the code, starting with the first step of picking N random points and colors as one might expect.

typedef union
{
    uint32_t value;
    struct
    {
        uint32_t r : 8;
        uint32_t g : 8;
        uint32_t b : 8;
        uint32_t a : 8;
    };
} rgba_t;

typedef struct
{
    int x;
    int y;
    rgba_t rgba;
} point_t;

static void randomize(
    point_t *points,
    const int n,
    const int w,
    const int h,
    const unsigned int seed
)
{
    point_t *p, *e;

    const int ww = w - 16;
    const int hh = h - 16;

    srand(seed);

    for(p = points, e = points + n; p != e; p++)
    {
        p->x = (rand() % ww) + 8;
        p->y = (rand() % hh) + 8;
        p->rgba = (rgba_t) {
            .r = ((rand() & 127) + 64),
            .g = ((rand() & 127) + 32),
            .b = ((rand() & 127) + 64),
            .a = 0xFF
        };
    }
}

And then the actual part where the magic happens.

static inline unsigned int distance(
    const point_t *p,
    const int x,
    const int y
)
{
    const int dx = p->x - x;
    const int dy = p->y - y;
    return dx * dx + dy * dy;
}

static void process(
    const point_t *points,
    const int n,
    rgba_t *restrict output,
    const int w,
    const int h
)
{
    int x, y, yo;
    unsigned int d, cd;
    const point_t *cp, *p, *e = points + n;

    for(y = 0; y < h; y++)
    {
        yo = y * w;

        for(x = 0; x < w; x++)
        {
            cd = UINT_MAX;
            cp = points;

            for(p = points; p != e; p++)
            {
                d = distance(p, x, y);
                if(d < cd)
                {
                    cd = d;
                    cp = p;
                }
            }

            output[x + yo] = cp->rgba;
        }
    }
}

Voilà!

voronoi

Worley Noise

What if instead of using random colors we used the distance to the point as the actual color?

That’s exactly what the so called Worley Noise is all about.

Before taking a look at the code, let’s see the results first in order to switch things up a wee bit.

voronoiw

The modifications to the process function can be seen below. The only thing worth calling out is the fact that we only take the sqrt of the distance once, which is still going to be expensive but it’s not end of the world.

static void process(
	const point_t *points,
	const int n,
	rgba_t *restrict output,
	const int w,
	const int h
)
{
	int x, y, yo;
	unsigned int d, cd;
	const point_t *p, *e = points + n;

	for(y = 0; y < h; y++)
	{
		yo = y * w;

		for(x = 0; x < w; x++)
		{
			cd = UINT_MAX;

			for(p = points; p != e; p++)
			{
				d = distance(p, x, y);
				if(d < cd)
				{
					cd = d;
				}
			}

			cd = sqrt(cd);
			output[x + yo] = (rgba_t) {
				.r = cd,
				.g = cd,
				.b = cd,
				.a = 0xFF
			};
		}
	}
}

Obviously, instead of picking random points, we could also place points on a grid an then simply displace them in order to have more fine grained control. Also, the distance could be manipulated in a gazillion ways in order to customize and alter the output even further.

Let’s take a quick look at what happens if we apply a simple step function.

#define step(a, b) ((a) < (b) ? 0 : 1)

cd = step(64, cd) * 0xFF;

voronows

Metaballs anyone?

Source Code

You can find the full source code below as per usual.

/*
    MIT LICENSE
    Copyright (c) 2023, Mihail Szabolcs
*/
#include <stdbool.h>
#include <stdint.h>
#include <stdlib.h>
#include <stdio.h>
#include <limits.h>
#include <math.h>
#include <time.h>

#define STB_IMAGE_WRITE_IMPLEMENTATION
#include "stb_image_write.h"

#ifndef UNUSED
	#define UNUSED(x) (void)(x)
#endif

#define DEFAULT_SIZE 1024
#define DEFAULT_NUM_POINTS 64

#define min(a, b) ((a) < (b) ? (a) : (b))
#define max(a, b) ((a) > (b) ? (a) : (b))
#define clamp(x, a, b) max(a, min(b, x))
#define step(a, b) ((a) < (b) ? 0 : 1)

typedef union
{
	uint32_t value;
	struct
	{
		uint32_t r : 8;
		uint32_t g : 8;
		uint32_t b : 8;
		uint32_t a : 8;
	};
} rgba_t;

typedef struct
{
	int x;
	int y;
#ifdef COLORS
	rgba_t rgba;
#endif
} point_t;

static void randomize(
	point_t *points,
	const int n,
	const int w,
	const int h,
	const unsigned int seed
);

static void process(
	const point_t *points,
	const int n,
	rgba_t *restrict output,
	const int w,
	const int h
);

int main(int argc, char *argv[])
{
	int w, h, n, ret;
	unsigned int seed;
	rgba_t *output;
	point_t *points;

	if(argc < 2)
	{
		fprintf(
        	stderr,
            "usage: %s output.png [[size] [num points] [seed]]\n",
            argv[0]
        );
		return EXIT_FAILURE;
	}

	if(argc > 2)
	{
		w = clamp(atoi(argv[2]), 64, 4096);
		h = w;
	}
	else
	{
		w = DEFAULT_SIZE;
		h = DEFAULT_SIZE;
	}

	if(argc > 3)
		n = clamp(atoi(argv[3]), 1, 4096);
	else
		n = DEFAULT_NUM_POINTS;

	if(argc > 4)
		seed = atoi(argv[4]);
	else
		seed = time(NULL);

	output = malloc(w * h * sizeof(rgba_t));
	if(output == NULL)
	{
		fprintf(stderr, "failed to allocate memory for output image\n");
		return EXIT_FAILURE;
	}

	points = malloc(n * sizeof(point_t));
	if(points == NULL)
	{
		fprintf(stderr, "failed to allocate memory for pointsn");
		free(output);
		return EXIT_FAILURE;
	}

	randomize(points, n, w, h, seed);
	process(points, n, output, w, h);

	ret = stbi_write_png(
        argv[1],
        w,
        h,
        sizeof(rgba_t),
        output,
        w * sizeof(rgba_t)
    );
	if(ret == 0)
	{
		fprintf(stderr, "failed to write output image: '%s'\n", argv[1]);
		ret = EXIT_FAILURE;
	}
	else
	{
		fprintf(stdout, "wrote output image to '%s' ...\n", argv[1]);
		ret = EXIT_SUCCESS;
	}

	free(points);
	free(output);
	return ret;
}

static void randomize(
	point_t *points,
	const int n,
	const int w,
	const int h,
	const unsigned int seed
)
{
	point_t *p, *e;

	const int ww = w - 16;
	const int hh = h - 16;

	srand(seed);

	for(p = points, e = points + n; p != e; p++)
	{
		p->x = (rand() % ww) + 8;
		p->y = (rand() % hh) + 8;
#ifdef COLORS
		p->rgba = (rgba_t) {
			.r = ((rand() & 127) + 64),
			.g = ((rand() & 127) + 32),
			.b = ((rand() & 127) + 64),
			.a = 0xFF
		};
#endif
	}
}

static inline unsigned int distance(
	const point_t *p,
	const int x,
	const int y
)
{
	const int dx = p->x - x;
	const int dy = p->y - y;
	return dx * dx + dy * dy;
}

static void process(
	const point_t *points,
	const int n,
	rgba_t *restrict output,
	const int w,
	const int h
)
{
	int x, y, yo;
	unsigned int d, cd;
	const point_t *p, *e = points + n;
#ifdef COLORS
	const point_t *cp;
#endif

	for(y = 0; y < h; y++)
	{
		yo = y * w;

		for(x = 0; x < w; x++)
		{
			cd = UINT_MAX;
#ifdef COLORS
			cp = points;
#endif

			for(p = points; p != e; p++)
			{
				d = distance(p, x, y);
				if(d < cd)
				{
					cd = d;
#ifdef COLORS
					cp = p;
#endif
				}
			}

#ifdef COLORS
			output[x + yo] = cp->rgba;
#else
			cd = sqrt(cd);

			// cd = step(STEP, cd) * 0xFF;

			output[x + yo] = (rgba_t) {
				.r = cd,
				.g = cd,
				.b = cd,
				.a = 0xFF
			};
#endif
		}
	}
}

/* vim: set ts=4 sw=4 sts=4 noet: */
https://mihail.co/2023/12/06/generating-voronoi-noise/
Upscaling Pixel Art
Show full content
Upscaling Pixel Art

A few years ago there was some sort of renaissance of pixel-art-upscalers, mainly fueled by the emulator community. It has gotten to a point where even some research division at Microsoft joined in and produced a paper around the subject.

The emulator community of course wanted to be able to “apply the upscaling” in real-time; as a direct result many of the so called upscalers were ported and made available as pixel shaders.

One such upscaling technique was called XBR and was the brainchild of the mysterious internet citizen Hyllian.

Not much is known about him, other than what can be gleaned from the trail of posts left behind on dying old-school forums like one.

XBR has many flavors an options, but the one that I was interested in has been known as XBR4x.

The 4x of course stands for the scale factor itself. Very creative, I know!

I finally decided to bite the bullet and come up with a relatively sane and simple implementation in C. Without too many bells and whistles of course, as per usual!

Before diving into any of the technical details, let’s take a look at some examples.

First this tiny house taken from the Tiny Swords asset pack by Pixel Frog.

And, then something more involved in the form of this amazing Dungeon Crawler Tileset from the open source rogue-like adventure called Dungeon Crawl Stone Soup.

Most implementations out there convert from RGB to YUV and employ all sorts of magical formulas to calculate the distance (or similarity if wish) between two pixels.

As it turn out, it’s more than enough to just use an approximation of [luma][luma] (Y’ not to be confused with Y) and still get more than decent results. This really simplifies and speeds up the implementation quite a bit without sacrificing too much.

static inline unsigned int rgba_distance(
    const rgba_t a,
    const rgba_t b
)
{
    const int dr = a.r - b.r;
    const int dg = a.g - b.g;
    const int db = a.b - b.b;

    const int ddr = dr * dr;
    const int ddg = dg * dg;
    const int ddb = db * db;

    return ((ddr << 1) + ddr) + (ddg << 2) + (ddb << 1);
}

The original formula to get Y' as seen on the link above looks like this:

0.299 * R + 0.587 * G + 0.114 * B

Notice how adding the weights together produces 1.

0.299 + 0.587 + 0.114 = 1.0

Now, we want to avoid doing 3 floating point multiplications, so we convert the weights into integers.

2 + 5 + 1 = 8

This means that the formula becomes:

(2 * R + 5 * G + B) / 8

It feels a bit heavy on the green channel and we can tweak it a bit, so we end up with:

(3 * R + 4 * G + 2 * B) / 10

Since we only use the computed distance relative to another distance, we can safely do away with normalizing the sum, which means that we don’t have to divide by 10.

With all this in place we can transform it into its final form by using only bitwise operations.

((r << 1) + r) + (g << 2) + (b << 1)

I just want to call out that your mileage may vary, there’s no absolute right or definitive way of doing this.

Hopefully, this little disclaimer seen above will be enough to dissuade anyone from denouncing me to the information superhighway color/gamma correction police and related organizations.

The interpolation (weighted average) between two pixels is also implemented with bitwise operations only.

static inline rgba_t rgba_interpolate(
    const rgba_t a,
    const rgba_t b
)
{
    return (rgba_t) {
        .r = (a.r + b.r) >> 1,
        .g = (a.g + b.g) >> 1,
        .b = (a.b + b.b) >> 1,
        .a = (a.a + b.a) >> 1
    };
}

Take a look at the full source code below.

/*
    MIT LICENSE
    Copyright (c) 2023, Mihail Szabolcs
*/
#include <stdbool.h>
#include <stdint.h>
#include <stdlib.h>
#include <stdio.h>

#define STBI_NO_HDR
#define STBI_NO_LINEAR
#define STB_IMAGE_IMPLEMENTATION
#define STB_IMAGE_WRITE_IMPLEMENTATION
#include "stb_image.h"
#include "stb_image_write.h"

#ifndef UNUSED
	#define UNUSED(x) (void)(x)
#endif

#define min(a, b) ((a) < (b) ? (a) : (b))
#define max(a, b) ((a) > (b) ? (a) : (b))

typedef union
{
	uint32_t value;
	struct
	{
		uint32_t r : 8;
		uint32_t g : 8;
		uint32_t b : 8;
		uint32_t a : 8;
	};
} rgba_t;

#define rgba_equal(a, b) ((a).value == (b).value)
#define rgba_not_equal(a, b) ((a).value != (b).value)

static void process(
	const rgba_t *restrict input,
	rgba_t *restrict output,
	const int iw,
	const int ih,
	const int ow,
	const int oh
);

int main(int argc, char *argv[])
{
	int w, h, bpp, ret;
	rgba_t *input, *output;

	if(argc != 3)
	{
		fprintf(stderr, "usage: %s input.png output.png\n", argv[0]);
		return EXIT_FAILURE;
	}

	input = (rgba_t *) stbi_load(argv[1], &w, &h, &bpp, sizeof(rgba_t));
	if(input == NULL)
	{
		fprintf(stderr, "failed to load input: %s\n", stbi_failure_reason());
		return EXIT_FAILURE;
	}

	output = malloc(w * 4 * h * 4 * sizeof(rgba_t));
	if(output == NULL)
	{
		fprintf(stderr, "failed to allocate memory for output image\n");
		stbi_image_free(input);
		return EXIT_FAILURE;
	}

	process(input, output, w, h, w * 4, h * 4);

	ret = stbi_write_png(
        argv[2],
        w * 4,
        h * 4,
        sizeof(rgba_t),
        output,
        w * 4 * sizeof(rgba_t)
    );
	if(ret == 0)
	{
		fprintf(stderr, "failed to write output image: '%s'\n", argv[2]);
		ret = EXIT_FAILURE;
	}
	else
	{
		fprintf(stdout, "wrote output image to '%s' ...\n", argv[2]);
		ret = EXIT_SUCCESS;
	}

	free(output);
	stbi_image_free(input);
	return ret;
}

static inline unsigned int rgba_distance(
	const rgba_t a,
	const rgba_t b
)
{
	const int dr = a.r - b.r;
	const int dg = a.g - b.g;
	const int db = a.b - b.b;

	const int ddr = dr * dr;
	const int ddg = dg * dg;
	const int ddb = db * db;

	return ((ddr << 1) + ddr) + (ddg << 2) + (ddb << 1);
}

static inline rgba_t rgba_interpolate(
	const rgba_t a,
	const rgba_t b
)
{
	return (rgba_t) {
		.r = (a.r + b.r) >> 1,
		.g = (a.g + b.g) >> 1,
		.b = (a.b + b.b) >> 1,
		.a = (a.a + b.a) >> 1
	};
}

static inline rgba_t rgba_interpolate13(
	const rgba_t a,
	const rgba_t b
)
{
	return (rgba_t) {
		.r = (a.r + ((b.r << 1) + b.r)) >> 2,
		.g = (a.g + ((b.g << 1) + b.g)) >> 2,
		.b = (a.b + ((b.b << 1) + b.b)) >> 2,
		.a = (a.a + ((b.a << 1) + b.a)) >> 2
	};
}

static inline rgba_t rgba_interpolate31(
	const rgba_t a,
	const rgba_t b
)
{
	return (rgba_t) {
		.r = (((a.r << 1) + a.r) + b.r) >> 2,
		.g = (((a.g << 1) + a.g) + b.g) >> 2,
		.b = (((a.b << 1) + a.b) + b.b) >> 2,
		.a = (((a.a << 1) + a.a) + b.a) >> 2
	};
}

static void kernel(
	const rgba_t pe,
	const rgba_t pi,
	const rgba_t ph,
	const rgba_t pf,
	const rgba_t pg,
	const rgba_t pc,
	const rgba_t pd,
	const rgba_t pb,
	const rgba_t f4,
	const rgba_t i4,
	const rgba_t h5,
	const rgba_t i5,
	rgba_t *n15,
	rgba_t *n14,
	rgba_t *n11,
	rgba_t *n3,
	rgba_t *n7,
	rgba_t *n10,
	rgba_t *n13,
	rgba_t *n12
)
{
	unsigned int e, i, ke, ki;
	bool be, bi;
	rgba_t px;

	if(rgba_equal(pe, ph) || rgba_equal(pe, pf))
		return;

    e = (
		rgba_distance(pe, pc) +
		rgba_distance(pe, pg) +
		rgba_distance(pi, h5) +
		rgba_distance(pi, f4)
	) + (rgba_distance(ph, pf) << 2);

	i = (
		rgba_distance(ph, pd) +
		rgba_distance(ph, i5) +
		rgba_distance(pf, i4) +
		rgba_distance(pf, pb)
	) + (rgba_distance(pe, pi) << 2);

	if(e >= i)
		return;

	be = (rgba_not_equal(pe, pg) && rgba_not_equal(pd, pg));
	bi = (rgba_not_equal(pe, pc) && rgba_not_equal(pb, pc));

	ke = rgba_distance(pf, pg);
	ki = rgba_distance(ph, pc);

	px = (rgba_distance(pe, pf) <= rgba_distance(pe, ph)) ? pf : ph;

	if(be && bi && ke == 0 && ki == 0)
	{
		*n13 = rgba_interpolate13(*n13, px);
		*n12 = rgba_interpolate31(*n12, px);
		*n15 = *n14 = *n11 = px;
		*n10 = *n3 = *n12;
		*n7 = *n13;
	}
	else if(be && ((ke << 2) <= ki))
	{
		*n11 = rgba_interpolate13(*n11, px);
		*n13 = rgba_interpolate13(*n13, px);
		*n10 = rgba_interpolate31(*n10, px);
		*n12 = rgba_interpolate31(*n12, px);
		*n14 = *n15 = px;
	}
	else if(bi && (ke >= (ki << 2)))
	{
		*n14 = rgba_interpolate13(*n14, px);
		*n7 = rgba_interpolate13(*n7, px);
		*n10 = rgba_interpolate31(*n10, px);
		*n3 = rgba_interpolate31(*n3, px);
		*n11 = *n15 = px;
	}
	else
	{
		*n11 = rgba_interpolate(*n11, px);
		*n14 = rgba_interpolate(*n14, px);
		*n15 = px;
	}
}

static void process(
	const rgba_t *restrict input,
	rgba_t *restrict output,
	const int iw,
	const int ih,
	const int ow,
	const int oh
)
{
	int x, y, yo, ox, oy, ihh, iww, xn1, xn2, xp1, xp2;
    int yn1, yn2, yp1, yp2, yon1, yon2, yop1, yop2;
	int oxo1, oxo2, oxo3, oyo, oyo1, oyo2, oyo3;
	rgba_t a1, b1, c1, a0, pa, pb, pc, c4, d0, pd, pe;
    rgba_t pf, f4, g0, pg, ph, pi, i4, g5, h5, i5;
	rgba_t o0, o1, o2, o3, o4, o5, o6, o7, o8, o9, oa, ob, oc, od, oe, of;

	UNUSED(oh);

	iww = iw - 1;
	ihh = ih - 1;

	for(y = 0; y < ih; y++)
	{
		yn1 = max(y - 1,   0);
		yn2 = max(y - 2,   0);
		yp1 = min(y + 1, ihh);
		yp2 = min(y + 2, ihh);

		yon1 = yn1 * iw;
		yon2 = yn2 * iw;
		yop1 = yp1 * iw;
		yop2 = yp2 * iw;

		yo = y * iw;

		oy   = y << 2;
		oyo  = oy * ow;
		oyo1 = (oy + 1) * ow;
		oyo2 = (oy + 2) * ow;
		oyo3 = (oy + 3) * ow;

		for(x = 0; x < iw; x++)
		{
			xn1 = max(x - 1,   0);
			xn2 = max(x - 2,   0);
			xp1 = min(x + 1, iww);
			xp2 = min(x + 2, iww);

			a1 = input[xn1 + yon2];
			b1 = input[  x + yon2];
			c1 = input[xp1 + yon2];

			a0 = input[xn2 + yon1];
			pa = input[xn1 + yon1];
			pb = input[  x + yon1];
			pc = input[xp1 + yon1];
			c4 = input[xp2 + yon1];

			d0 = input[xn2 + yo];
			pd = input[xn1 + yo];
			pe = input[  x + yo];
			pf = input[xp1 + yo];
			f4 = input[xp2 + yo];

			g0 = input[xn2 + yop1];
			pg = input[xn1 + yop1];
			ph = input[  x + yop1];
			pi = input[xp1 + yop1];
			i4 = input[xp2 + yop1];

			g5 = input[xn1 + yop2];
			h5 = input[  x + yop2];
			i5 = input[xp1 + yop2];

			o0 = o1 = o2 = o3 = o4 = o5 = o6 = o7 = o8 = o9 = pe;
            oa = ob = oc = od = oe = of = pe;

			kernel(
				pe, pi, ph, pf, pg, pc, pd, pb, f4, i4, h5, i5,
				&of, &oe, &ob, &o3, &o7, &oa, &od, &oc
			);
			kernel(
				pe, pc, pf, pb, pi, pa, ph, pd, b1, c1, f4, c4,
				&o3, &o7, &o2, &o0, &o1, &o6, &ob, &of
			);
			kernel(
				pe, pa, pb, pd, pc, pg, pf, ph, d0, a0, b1, a1,
				&o0, &o1, &o4, &oc, &o8, &o5, &o2, &o3
			);
			kernel(
				pe, pg, pd, ph, pa, pi, pb, pf, h5, g5, d0, g0,
				&oc, &o8, &od, &of, &oe, &o9, &o4, &o0
			);

			ox   = x << 2;
			oxo1 = ox + 1;
			oxo2 = ox + 2;
			oxo3 = ox + 3;

			output[  ox +  oyo] = o0;
			output[oxo1 +  oyo] = o1;
			output[oxo2 +  oyo] = o2;
			output[oxo3 +  oyo] = o3;
			output[  ox + oyo1] = o4;
			output[oxo1 + oyo1] = o5;
			output[oxo2 + oyo1] = o6;
			output[oxo3 + oyo1] = o7;
			output[  ox + oyo2] = o8;
			output[oxo1 + oyo2] = o9;
			output[oxo2 + oyo2] = oa;
			output[oxo3 + oyo2] = ob;
			output[  ox + oyo3] = oc;
			output[oxo1 + oyo3] = od;
			output[oxo2 + oyo3] = oe;
			output[oxo3 + oyo3] = of;
		}
	}
}

/* vim: set ts=4 sw=4 sts=4 noet: */

At first glance, it doesn’t look very efficient now, does it? That’s correct, it’s stinks of branches, but it’s still plenty fast as-is.

When upscaling the house with an original resolution of 128x256 to 512x1024.

$ time build/xbr4x house.png house4x.png
wrote output image to 'house4x.png' ...

real 0m0.061s
user 0m0.057s
sys	 0m0.004s

$ time build/xbr4x house.png house4x.png
wrote output image to 'house4x.png' ...

real 0m0.060s
user 0m0.056s
sys	 0m0.004s

$ time build/xbr4x house.png house4x.png
wrote output image to 'house4x.png' ...

real 0m0.058s
user 0m0.054s
sys	 0m0.004s

When upscaling the tileset with an original resolution of 256x256 to 1024x1024.

$ time build/xbr4x tileset.png tileset4x.png
wrote output image to 'tileset4x.png' ...

real 0m0.156s
user 0m0.148s
sys	 0m0.007s

$ time build/xbr4x tileset.png tileset4x.png
wrote output image to 'tileset4x.png' ...

real 0m0.147s
user 0m0.141s
sys	 0m0.005s

$ time build/xbr4x tileset.png tileset4x.png
wrote output image to 'tileset4x.png' ...

real 0m0.136s
user 0m0.124s
sys	 0m0.010s

There are some low-hanging fruits that one could optimize. A prime example of this would be the clamping.

Instead of clamping to border, we could clamp to bounds.

xn1 = (x - 1) % iw
yn1 = (y - 1) % ih

Considering the fact that we very likely going to be upscaling and working with textures, it wouldn’t be too far off to suggest that we could also guarantee that their size to be power of two.

Having that little extra constraint, we could go one step further and replace the % with a bitwise &.

xn1 = (x - 1) & (iw - 1)
yn1 = (y - 1) & (ih - 1)

This works because P % N == P & (N -1), only when N is power of two.

On the other hand, we could also eliminate the need for clamping altogether by adding a 2 pixel wide border around the input image, which would guarantee that we never sample out of bounds.

At any rate this implementation is not something that you would be using in real-time, but rather offline as part of your asset pipeline or at run-time when loading your assets.

The second scenario could come in especially handy on platforms where the initial download size still matters, like the web or mobile.

It would also open the door to upscale once on load and then persist the results to disk and subsequent runs could just use the upscaled versions of the assets. Of course special care would have to be taken to invalidate the cache on asset updates, etc.

I’ll have to take a look at machine learning based upscaling at some point or another and see how something like that fairs compared to something with a handcrafted kernel like xbr4x.

https://mihail.co/2023/12/05/upscaling-pixel-art/
2023 November Retrospective
Show full content
2023 November Retrospective

Now that November is all said and done, the glorious month of December can begin, which is the month of the year when nothing really gets done.

A while back, I wrote a tiny Tamper Monkey script that would inject some extra buttons into Steam App pages. I did this to make it easier to launch tools like Steam Spy, without having to go through the pain of searching for a particular game by its game id and so forth.

suika

I ended up cleaning it up a little bit and added Gamalytic into the mix, as can be seen in the screenshot above.

You can find the integral source code below. It’s not much, but it’s honest work.

// ==UserScript==
// @name         Extra Steam Buttons
// @namespace    https://mihail.co
// @version      0.2
// @description  Adds extra buttons to Steam.
// @author       Mihail Szabolcs
// @match        https://store.steampowered.com/app/*
// @grant        none
// ==/UserScript==

(function()
{
    'use strict';

    const add_button = (title, url, margin, match_id_only) =>
    {
        const el = document.querySelector('.apphub_OtherSiteInfo');
        if(!el) {
            return;
        }

        const a = el.querySelector('a');
        if(!a) {
            return;
        }

        const button = a.cloneNode(true);
        const path = a.href.match(/\/app\/([0-9]+)/)[match_id_only ? 1 : 0];

        button.href = url + path;
        button.target = '_blank';

        if(margin && margin > 0) {
          button.style.marginLeft = margin + 'px';
        }

        const text = button.querySelector('span');
        if(!text) {
            return;
        }
        text.innerHTML = title;

        el.appendChild(button);
    }

    add_button('Steam Spy', 'https://steamspy.com');
    add_button('Steam Charts', 'https://steamcharts.com', 3);
    add_button('Gamalytic', 'https://gamalytic.com/game/', 3, true);
})();

If you were wondering what on earth is Suika Game. Well, it appears that it’s the next best viral game out there. It’s a combination of Tetris and 2048.

The official version is available on the Nintendo Switch Store.

It’s extremely addictive, so be careful. Consider yourself warned! Maybe it’s time for a new DOS & boot-able viral game. You’ve heard it here first.

Almost forgot, I also ended up trying out yet another vampire survivors-like this month called Death Must Die.

I will have to make list or some sort of a top of all the vampire survivors-likes that I ended up playing during the past year and a half or so.

There’s a TODO item for the final retrospective of the year at end of December.

https://mihail.co/2023/11/30/2023-november-retrospective/
2023 October Retrospective
Show full content
2023 October Retrospective

While last month was the month of the Nun II, this month turned out to be the month of 30 Coins.

It’s been a long time coming due to all the things that have went down in the past few years. However, as expected it started out strong and with a bang.

I didn’t realize until about two or three years ago that I’ve been missing out on a lot of amazing series coming out of Spain and Italy, to name a few in no particular order since they are all terrific:

If you do decide to bite and watch any of them, please do so with subtitles, dubs take the soul out of highly original productions like these, regardless of how skilled the actors who do end up dubbing them are. It’s just the nature of the beast.

Did I just turn into some movie/series aficionado all the sudden? Is this my version of a mid-life crisis?Far from it, I always been a huge movie/series buff, or at least ever since I can remember; I just never ended up talking about it extensively until relatively recently.

Reverse Engineering Series

In other news, I thought that I’d bring back a topic that I really spent a lot of my time with, many many moons ago, well a life-time ago now to be more precise, which is reverse engineering.

Why now? Well, the 20th anniversary of Dino and Aliens by Nevosoft will be coming up shortly, and it’s really the perfect target for a reverse engineering series. No compression, no obfuscation, no anti-debugger tricks, just pure love compressed and contained in a few megabytes.

It’s a tiny and super fun shareware game from 2004, which is pretty much abandon-ware now without any way to buy it anymore, at least not officially, but the 60 minute trial (demo) version is still available, and that’s what I really need anyway for such a series.

I looked at it a few years ago and wrote a tiny packer and unpacker for the package file format it uses, but never really went any deeper than that and this month I thought that it might just be what the doctor ordered to vary things up even more. Turning on the heat, aren’t I?

Now of course, as you very well know as an avid reader of this blog that I always rotate between several projects at any given time, so this will just yet another one. As a result, do not expect any precise time-lines or dates. It will be up when it will be ready to be up, no promises.

I just thought that I’d make a formal announcement since the idea of doing this as a 20th anniversary thing made me all fuzzy and warm inside.

This will also serve as the perfect opportunity for me and Ghidra to get to know each other a little bit better.

Hey, cutie snek! Too cringe? Okay, I know where the door is.

https://mihail.co/2023/10/31/2023-october-retrospective/
2023 September Retrospective
Show full content
2023 September Retrospective

September was the month of the Nun II, and as a result I decided to brighten up the place a little bit.

I haven’t had a chance to watch it yet, which of course is good, because it means that I can’t spoil it for you either. 9999 IQ thinking right?

Unity Drama

In other news, unless you have been living under a rock, you probably have heard about the Unity drama and the total chaos that followed. I think it’s fair to say that this fiasco done irreparable damage to the Unity brand.

Now, I am not here to shit on Unity since plenty of that has been done already and it’s not fun anymore anyway, but I do want to touch on a few things.

Before I get into the nitty-gritty, let’s clarify something rather important. When I say indie game developer or studio, I mean having a team with a headcount between 1 and 20 people or thereabouts.

With anything bigger than that, the lines are starting blur a little bit and various degrees of so called AA-ness will start entering the picture.

So, if you are an indie, there’s absolutely no good reason to use an off the shelf engine or toolkit. Why?

Basing your entire livelihood and business, on third party licensed technology, in many cases without access to source code is just not the brightest of ideas. And, you really do not need to have graduated business school with a young american merit scholarship to figure that much out. It’s just good old plain common sense.

But, but, but wait a minute. Isn’t it like super hard and difficult to build your own tech? Oh and also it has to be portable, isn’t that even harder? What about mobile, what about consoles, what about …?

This is a very common misconception and this is exactly why people end up locking themselves into a black box and many times leave metric tons of money on the table by refusing to port their game at all, or throwing crazy amounts of money at third parties who in many cases do an absolutely dreadful job at porting their games. To make things worse, many times these ports are then never updated and they simply sit there and rot, which in turn ends up causing even more damage.

I spent quite a lot thinking about this, trying to figure out where this line of thinking is coming from and how it managed to permeate most of the industry, and I believe that I managed to come up with a pretty reasonable theory one might say.

During the 90s when Carmack and Id Software were pushing the envelope and kept outdoing themselves with every single new release sent a shock-wave through the industry and we can still feel its after-effects even today in some form or another. This created a rift and split the industry into primarily two main camps.

One camp said that they can’t possible to do all that wizardry in house, which led to the creation of early engine licensing business.

The second camp said that not only they can build similar tech, but even surpass it, which in turn ended up providing the engines to the first camp and many that embarked on this journey over the years ended up abandoning any in house development of games and focused purely on the tech.

Both of these camps were so razzle-dazzled by the pixel eye candy on their monitors that they completely missed the genius of Carmack and Id Software, and many of them continue to do so even today.

What do I mean by the genius of Carmack? Every single game built and released by Id Software ran on a wide range of hardware, which meant that little Timmy from Nebraska could probably play the games on their daddy’s business PC, admittedly with less eye-candy.

So let’s take a deeper look. The first camp thought that by licensing the engine they could not only save money and time but end up building a better game. While this line of the thinking seems very sensible and reasonable on paper to a bunch of people wearing tweed jackets all hoping to be the next Bill Gates, it doesn’t really work in practice. Why?

Because every single game will have some specific needs, some of which might not be very obvious from the get go and while in these early days one would get full source code access when licensing an engine, often times by the end of the development cycle almost nothing was left from the original engine that the game started out with.

In many other cases, the game just ended up feeling like a “cheap reskin” that even retained some of the same bugs.

So what is there to learn from all this? First, if you must license the tech, pick something with full source code access that lends itself well to customization and makes it relatively painless to replace entire subsystems if need be. This usually is not the case, unless the entire stack was built with this in mind from the get go, which 99.99% of the time is not the case, trust me!

Second, do not fall into the trap that you’ll be able to just roll with off the shelf tech, especially when the game itself doesn’t fall into what one would consider conventional game design, or perhaps it’s a completely new genre. And when it does, there might be just one thing that needs some deeper customization in order to feel just right and fit in.

And now let’s look at the second camp, the camp that accepted the challenge and started building their own in-house tech that matched and surpassed what Carmack ended up hacking on.

One of the crucial things that they missed is the fact that I touched on earlier, and that is the versatility of the tech built by Carmack, that just worked on almost anything, from your grandma’s toaster to new and shiny tower in your uncle Billys’ mancave. The second camp only focused on the proverbial high-end and if you look at the industry today this still rings true.

The message to the customers (“gamers”) is a resounding “Just upgrade!”, which of course is totally fine if your AA-ness is pretty high and you can afford losing sometimes a significant amount of sales and still break even, but it’s not so ideal if you are an indie where losing those sales might make a difference between staying afloat or going down the way of the dodo.

Yet another fatal mistake that many of the contenders in this second camp have done is the abandonment of all in-house development of games. Building any tech without using said tech in-house is a fatal mistake and any company that does so loses valuable insight, which simply cannot be replaced by feedback or insight coming from the outside.

Often times your customers do not know what they do not know, or even what they actually need and they look towards you for guidance. And you cannot possibly give any meaningful guidance if you aren’t using your own tech internally, it’s as simple as that.

In addition this also leads to simply “copy-ing” what the so called competition is doing, because the thinking is that if they are doing it: for one it must good and second that you’ll potentially miss out if you also do not have the so called elusive “killer” feature or features.

This line of thinking is the arch enemy of any sustainable business and leads only to bloat and the decay in quality as well as confidence in the tech; out of which the only people who end up winning will be the people at the top who most of the time have actually zero clue of what is actually going on or what are the real and tangible consequences of their strategy, while the people who do the work and many times might speak up and try to ring the alarm bells get nothing but a sour taste in their mouths and become filled with hopelessness and disillusionment.

All this to say that if you are an indie, your safest and best bet is still to roll your own tech, especially more so if you are building 2D games. Do not let anybody tell you otherwise and convince you that you are not capable, by using the age of lie that goes something like this: “build games, not engines”. It’s all a crock of shit and nothing more than ravings of underachiever mad-men who would do anything to feel even slightly better about themselves.

Dad Joke

I am going to end it on a positive note with an albeit cringe, but very fitting and thematic dad joke!

Q: What did Valak say to Loraine?

A: It’s NUN of your business!

Sorry, I’ll see myself out now!

https://mihail.co/2023/09/30/2023-september-retrospective/
2023 August Retrospective
Show full content
2023 August Retrospective

As a Vim aficionado, I was totally surprised and shocked to learn about the sudden passing of Bram Moolenar, at the beginning of this month.

This means that Vim 9 is probably the last and definitive version of Vim, considering that it’s not clear who will be the next BDFL to take the helms of development. In fact, it’s highly likely that there won’t be another BDFL at all, which is even more terrifying, if you ask me.

In other news, this August also marked one year since the disappearance of Our Machinery. I never talked about it earlier, because I felt that it would be in poor taste, but now that a year has passed, I thought that I’d mention it. We still don’t know of course what has transpired and one can only theorize. But, if you ask me, some sort of legal trouble would be my personal best guess.

Alas, I didn’t get to play or work much on any of my side projects, but I do have some pretty cool stuff cooking in the proverbial oven, one of them being the build system that I’ve mentioned and talked about in various forms in the past. Would be nice to release an experimental version of it before the end of the year. We’ll see how things go, not going to make any hard promises yet.

https://mihail.co/2023/08/31/2023-august-retrospective/
2023 July Retrospective
Show full content
2023 July Retrospective

This month I wrote about anaglyphs and created a tiny interactive image difference viewer for the blog, since I’ve been posting quite a lot of content that process images or just need to show visual difference in one way or another lately.

I also added a few more banners to the blog, because I was getting tired of rotating between the same seven or so for the past couple of years. This of course adds zero value for anyone reading the blog through their favorite RSS reader. Oh, well, still worth it.

The Legend of Zelda: Tears of the Kingdom

My next step is to uncover the entire map in the depths in the hopes of actually being able to complete the quest that will lead to the shrine sensor. Following the statues is just not working for me and I don’t want to start looking up guides and tutorials, at least not yet anyway!

As you can see in the short clip above, I still have quite a long way to achieve this. For me when it comes to this game (and it was also the case with BoTW) it’s not about just quickly running through the story and boom, be done with it, but rather explore every nook and cranny at my own pace.

Needless to say, I will be playing and savoring it for many months to come.

In addition to ToTK, I’ve also been playing quite a lot of Soulstone Survivors, which is one of the better balanced and less-buggy Vampire Survivors clones currently out there on Steam.

https://mihail.co/2023/07/31/2023-july-retrospective/
Image Diff(erence) Viewer
Show full content
Image Diff(erence) Viewer

Considering that I’ve been posting quite a lot of things that perform image manipulation in one form or another, I thought that it’s high-time to build one of those fancy image difference viewers that one can use and slide between two images in a seamless fashion.

As I don’t intend to release this as a separate standalone project, I thought that I’d just share the entire thing here, considering that it’s only a handful of lines of code and nothing too fancy.

JS
/*
	MIT LICENSE
	Copyright (c) 2023, Mihail Szabolcs
*/
document.querySelectorAll(".image-diff-viewer").forEach(function(el)
{
	var images = el.querySelectorAll("img");
	if(images.length != 2)
		return;

	var image = images[1];
	image.style.display = "block";

	el.removeChild(image);

	var wrapper = document.createElement("div");
	wrapper.className = "wrapper";
	wrapper.style.width = "50%";
	wrapper.appendChild(image);

	el.appendChild(wrapper);

	var update = function(e)
	{
		var rect = el.getBoundingClientRect();
		var border = Math.max(0, wrapper.offsetWidth - wrapper.clientWidth);
		var x = (e.pageX - rect.left - border) / el.offsetWidth;
		var perc = Math.min(Math.max(0, x * 100), 100);

		wrapper.style.width = perc + "%";
	};

	el.addEventListener("mousemove",  update, false);
    el.addEventListener("mouseleave", update, false);
	el.addEventListener("touchstart", update, false);
	el.addEventListener("touchmove" , update, false);
});
CSS
.image-diff-viewer
{
	position: relative;
	cursor: ew-resize;
	display: inline-block;
}

.image-diff-viewer img:last-child
{
	display: none;
}

.image-diff-viewer .wrapper
{
	position: absolute;
	width: auto;
	height: auto;
	top: 0;
	left: 0;
	overflow: hidden;
	border-right: 3px solid white;
	opacity: 0.75;
}
HTML
<div class="image-diff-viewer">
    <img src="image.png" />
    <img src="image_alt.png" />
</div>
Demo

You probably have seen it in action my previous post about anaglyphs, but feel free to check out the example below, just in case you missed it.

To use it, just to hover over the image and slide to the left or right. Touch should work as well. That is all!

https://mihail.co/2023/07/10/image-diff-viewer/
Anaglyphs
Show full content
Anaglyphs

Anaglyph is just a fancy word for the 3D stereoscopics effect achieved by offsetting and blending a red and cyan tinted version of an image.

anaglyph_glasses

To view such an image and observe the so called 3D effect, naturally one will need to have glasses with red an cyan colored lenses at hand.

As per usual, I went ahead and created a small command line utility that can process an input image and spit out its anaglyph counterpart.

Let’s take awe a gander at how this is done by taking a look at the code snippet below.

#define clamp(x, max) ((x) > (max) ? (max) : (x))

static void process(
    const rgba_t *restrict input,
    rgba_t *restrict output,
    const int w,
    const int h,
    const int dx
)
{
    int x, y, yo, i, ww;
    rgba_t c, a;

    ww = w - 1;

    for(y = 0; y < h; y++)
    {
        yo = y * w;
        for(x = 0; x < w; x++)
        {
            i = x + yo;
            c = input[i];

            a = input[clamp(x + dx, ww) + yo];
            c.r = a.r;

            output[i] = c;
        }
    }
}

We simply iterate over every pixel and sample ahead dx amount of pixels on the x axis, while also making sure that x stays within the bounds of the image.

Then, we replace the red channel of the current pixel with the red channel of the pixel we sampled, while leaving the green and blue channels untouched.

And now, drum rolls please! You can admire the results in all its glory. Slide over the image to view the difference. Special thanks go out to Big Buck Bunny for participating and being a trusty test subject.

If you pay attention and take a closer look to the right side of the image, you might notice how there are some artifacts that look quite bad to say the least.

bunny_artifacts_banding

These are caused by the fact that we clamp x to the range of 0 .. w - 1. With some images this might be less or barely noticeable, but still ugly and not proper at all.

Nonetheless, an easy way to get rid this issue would be to simply crop the output image. Presto, problem solved. But, there’s another way we can mitigate this without having to resort to something as drastic as cropping.

We simply never sample beyond the bounds, namely w - dx and then we simply copy the rest of the pixels from the input image as-is.

static void process(
    const rgba_t *restrict input,
    rgba_t *restrict output,
    const int w,
    const int h,
    const int dx
)
{
    int x, y, yo, i, ww;
    rgba_t c, a;

	ww = w - dx;

    for(y = 0; y < h; y++)
    {
        yo = y * w;

        for(x = 0; x < ww; x++)
        {
            i = x + yo;
            c = input[i];

            a = input[i + dx];
            c.r = a.r;

            output[i] = c;
        }

        for(x = ww; x < w; x++)
        {
            i = x + yo;
            output[i] = input[i];
        }
    }
}

While this is most definitely far from being perfect, it’s also not terrible. At any rate much better than the ugly artifacts and also a whole lot less noticeable, which is a plus.

Before I go much further, let’s take a look at what happens if we flip the channels around.

static void process(
    const rgba_t *restrict input,
    rgba_t *restrict output,
    const int w,
    const int h,
    const int dx
)
{
    int x, y, yo, i, ww;
    rgba_t c, a;

	ww = w - dx;

   	for(y = 0; y < h; y++)
    {
        yo = y * w;

        for(x = 0; x < ww; x++)
        {
            i = x + yo;
            c = input[i];

            a = input[i + dx];
            c.g = a.g;
            c.b = a.b;

            output[i] = c;
        }

        for(x = ww; x < w; x++)
        {
            i = x + yo;
            output[i] = input[i];
        }
    }
}

To observe the difference a wee bit better, let’s take a look another look at the two versions overlay-ed on top of each other.

In case you want to play around with this yourself, you can find the full listing of the utility below.

/*
    MIT LICENSE
    Copyright (c) 2023, Mihail Szabolcs
*/
#include <stdbool.h>
#include <stdint.h>
#include <stdlib.h>
#include <stdio.h>
#include <math.h>

#define STBI_NO_HDR
#define STBI_NO_LINEAR
#define STB_IMAGE_IMPLEMENTATION
#define STB_IMAGE_WRITE_IMPLEMENTATION
#include "stb_image.h"
#include "stb_image_write.h"

#ifndef UNUSED
	#define UNUSED(x) (void)(x)
#endif

#ifndef DEFAULT_X_OFFSET
	#define DEFAULT_X_OFFSET 32
#endif

typedef union
{
	uint32_t rgba;
	struct
	{
		uint32_t r : 8;
		uint32_t g : 8;
		uint32_t b : 8;
		uint32_t a : 8;
	};
} rgba_t;

static void process_red(
	const rgba_t *restrict input,
	rgba_t *restrict output,
	const int w,
	const int h,
	const int dx
);
static void process_cyan(
	const rgba_t *restrict input,
	rgba_t *restrict output,
	const int w,
	const int h,
	const int dx
);

int main(int argc, char *argv[])
{
	int w, h, dx, bpp, ret;
	bool flip;
	rgba_t *input, *output;

	if(argc < 3)
	{
		fprintf(stderr, "usage: %s input.png output.png [dx]\n", argv[0]);
		return EXIT_FAILURE;
	}

	if(argc > 3)
	{
		dx = atoi(argv[3]);
	}
	else
	{
		dx = DEFAULT_X_OFFSET;
	}

	flip = dx < 0;

	if(flip)
	{
		dx = -dx;
	}

	input = (rgba_t *) stbi_load(argv[1], &w, &h, &bpp, sizeof(rgba_t));
	if(input == NULL)
	{
		fprintf(
            stderr,
            "failed to load input: %s\n",
            stbi_failure_reason()
        );
		return EXIT_FAILURE;
	}

	output = malloc(w * h * sizeof(rgba_t));
	if(output == NULL)
	{
		fprintf(stderr, "failed to allocate memory for output image\n");
		stbi_image_free(input);
		return EXIT_FAILURE;
	}

	if(flip)
       	process_cyan(input, output, w, h, dx);
    else
		process_red(input, output, w, h, dx);

	ret = stbi_write_png(
        argv[2],
        w,
        h,
        sizeof(rgba_t),
        output,
        w * sizeof(rgba_t)
    );
	if(ret == 0)
	{
		fprintf(stderr, "failed to write output image: '%s'\n", argv[2]);
		ret = EXIT_FAILURE;
	}
	else
	{
		fprintf(stdout, "wrote output image to '%s' ...\n", argv[2]);
		ret = EXIT_SUCCESS;
	}

	free(output);
	stbi_image_free(input);
	return ret;
}

static void process_red(
	const rgba_t *restrict input,
	rgba_t *restrict output,
	const int w,
	const int h,
	const int dx
)
{
	int x, y, yo, i, ww;
	rgba_t c, a;

	ww = w - dx;

	for(y = 0; y < h; y++)
	{
		yo = y * w;

		for(x = 0; x < ww; x++)
		{
			i = x + yo;
			c = input[i];

			a = input[i + dx];
			c.r = a.r;

			output[i] = c;
		}

		for(x = ww; x < w; x++)
		{
			i = x + yo;
			output[i] = input[i];
		}
	}
}

static void process_cyan(
	const rgba_t *restrict input,
	rgba_t *restrict output,
	const int w,
	const int h,
	const int dx
)
{
	int x, y, yo, i, ww;
	rgba_t c, a;

	ww = w - dx;

	for(y = 0; y < h; y++)
	{
		yo = y * w;

		for(x = 0; x < ww; x++)
		{
			i = x + yo;
			c = input[i];

			a = input[i + dx];
			c.g = a.g;
			c.b = a.b;

			output[i] = c;
		}

		for(x = ww; x < w; x++)
		{
			i = x + yo;
			output[i] = input[i];
		}
	}
}

/* vim: set ts=4 sw=4 sts=4 noet: */

Damn, I nearly dated myself there for a moment. Oh well, at least I didn’t say subroutine.

Naturally, not all images will lend themselves well to this technique and more than likely the offset will have to be tweaked on a per image basis in order to achieve even remotely half decent results.

After taking a peek at the code above, I am sure that it might have occurred to you, if this could be applied in real time as a filter when playing video or a video game?

The answer to that question is a resounding yes. Ancient Astronaut theorists also seem to agree and suggest that further clues can be found by examining the fragment shader recently discovered in the stepped pyramid which served as the tomb and resting place of Kukulkan.

#define SHADER_SOURCE(...) #__VA_ARGS__

const char * const FRAGMENT_SHADER_SOURCE = SHADER_SOURCE(
	uniform sampler2D texture;
    uniform vec3 offset;
    varying vec2 uv;

    void main()
    {
        vec4 a = texture2D(texture, uv);
        vec4 b = texture2D(texture, uv + offset.xy);

        float s = step(uv.x, offset.z);
        gl_FragColor = mix(a, vec4(a.r, b.g, b.b, b.a), s);
    }
);

It assumes that we are rendering a full screen quad using a 2D sampler with its UV coordinate wrapping set to GL_CLAMP_TO_EDGE.

The value of the offset uniform would be something like:

vec3(0.01, 0.0, 1.0 - 0.01);

In the context of a game, you’d render into two separate render targets and then sample and mix the channels together in a very simple and dumb fragment shader. This would probably give you the best bang for your buck.

#define SHADER_SOURCE(...) #__VA_ARGS__

const char * const FRAGMENT_SHADER_SOURCE = SHADER_SOURCE(
	uniform sampler2D texture_leye;
    uniform sampler2D texture_reye;
    varying vec2 uv;

    void main()
    {
        vec4 a = texture2D(texture_leye, uv);
        vec4 b = texture2D(texture_reye, uv);

        gl_FragColor = vec4(a.r, b.g, b.b, b.a);
    }
);

Alternatively, you could avoid using render targets, and render twice with additive blending. This can also be done with just fixed function pipeline without the need for any shaders or multi-texturing for that matter. Perfect for the scenario when you’d really want to bring grandma’s PC stuck in the attic alive with only a few lines of code.

It should go without saying that this is the most basic way of constructing anaglyphs and there are several more advanced variations and approaches out there in the great unknown. So please, don’t consider this as some authoritative technique and reference implementation.

Personally, I always wondered how come stereoscopy as a whole has never seen a more widespread adoption, especially in more recent years.

But then again, VR is also struggling, and what these two have in common? The need for an additional accessory, in other words a pair of special glasses.

As it turns out, people really seem to be in a love hate relationship glasses, so much so, that this innate hate spawned an entire industry. I am talking about contact lenses of course.

https://mihail.co/2023/07/08/anaglyphs/
2023 June Retrospective
Show full content
2023 June Retrospective

This month I ended up writing about branchless line drawing, which was just a tiny side project that came out of a shower thought that I had a few weeks back.

suzanne

The Legend of Zelda: Tears of the Kingdom

I am around 62 hours into the game and it still feels like I just scratched the surface. So far, I focused on uncovering the entire surface level map by finding all the towers, and having full stamina. Which means that I found quite a lot of the shrines without having the shrine sensor.

One thing that I consider as a minor gripe is that in order to get your shrine sensor and autobuild ability, you have to do some elaborate quests in the depths. Would be interesting to see some stats around how many people finished the game without going through the trouble of getting them.

The next step is to start getting some hearts, because rolling with only 6 isn’t cutting it anymore. Oh and would be nice to start enchanting some of the armor sets at the great fairies for a wee bit of extra oomph.

Oh and is it just me, or the blood moons are happening a lot more often now, compared to Breath of the Wild?

https://mihail.co/2023/06/30/2023-june-retrospective/
Branchless Line Drawing
Show full content
Branchless Line Drawing

When it comes to drawing non-antialised lines, undoubtedly the very first approach that comes to mind is of course Bresenham’s line drawing algorithm.

I always dreaded implementing it, simply because it’s full of special cases; and special cases generally mean branches, which are the last things you’d want anywhere near tight loops.

A couple of weeks ago, I had a shower thought. Why not just use linear interpolation and simply interpolate between the two points?

Naturally, I was almost certain that someone, somewhere, already thought of this and perhaps even patented it as folks do with these things.

After a cursory search on the information super highway I confirmed that my gut instinct was right, however the implementations I found still used branches and superfluous divisions, so I decided to roll and share my own flavor.

void image_render_line(
	image_t *image,
	const point_t p0,
	const point_t p1,
	const color_t color
)
{
	color_t *pixels = image->pixels;
	const int w = image->w;

	const float x0 = p0.x;
	const float y0 = p0.y;
	const float dx = p1.x - x0;
	const float dy = p1.y - y0;

	const float step = 1.0f / fmaxf(fabsf(dx), fabsf(dy));

	for(float t = 0.0f; t < 1.0f; t += step)
	{
		const int x = x0 + dx * t;
		const int y = y0 + dy * t;

		pixels[x + y * w] = color;
	}
}

Let’s also take a peek at the disassembly, just for funsies no less.

 ; cc -O2 -ffast-math -masm=intel -S line.c
 image_render_line:
 	movq	r8, xmm0
 	movq	rax, xmm1
 	movdqa	xmm4, xmm1
 	mov	ecx, edi
 	shr	r8, 32
 	shr	rax, 32
 	subss	xmm4, xmm0
 	movdqa	xmm7, xmm0
 	movd	xmm3, r8d
 	movd	xmm1, eax
 	mov	edi, edx
 	movss	xmm5, DWORD PTR .LC2[rip]
 	subss	xmm1, xmm3
 	movss	xmm0, DWORD PTR .LC1[rip]
 	movaps	xmm6, xmm5
 	movaps	xmm2, xmm1
 	andps	xmm2, xmm0
 	andps	xmm0, xmm4
 	maxss	xmm0, xmm2
 	divss	xmm6, xmm0
 	pxor	xmm0, xmm0
 .L2:
 	movaps	xmm2, xmm1
 	mulss	xmm2, xmm0
 	addss	xmm2, xmm3
 	cvttss2si	eax, xmm2
 	movaps	xmm2, xmm4
 	mulss	xmm2, xmm0
 	addss	xmm0, xmm6
 	imul	eax, ecx
 	addss	xmm2, xmm7
 	cvttss2si	edx, xmm2
 	add	eax, edx
 	comiss	xmm5, xmm0
 	cdqe
 	mov	DWORD PTR [rsi+rax*4], edi
 	ja	.L2
 	ret

What’s the catch? There must be some caveats, right?

First of all, there is no clipping, so you must ensure that both points are within the viewport. In other words, the following must be true:

p0.x >= 0 && p0.x <= w - 1
p0.y >= 0 && p0.y <= h - 1
p1.x >= 0 && p1.x <= w - 1
p1.y >= 0 && p1.y <= h - 1

Second, it doesn’t ensure that t = 1.0f which means that line is not guaranteed reach the coordinates of the endpoint.

This of course could be addressed by drawing one additional point or simply changing t < 1.0f to t <= 1.0f and then making sure that t never goes beyond 1.0f.

Third, it doesn’t handle the case when both points are in the same position, in other words p0 == p1.

This could be solved as well relatively easily, by clamping the step value before the division, like illustrated below.

const float step = 1.0f / fmax(fmax(fabs(dx), fabs(dy)), 1.0f);

That’s it. And now, here’s an actual full fledged example, which outputs a lovely wireframe render of Suzanne.

/*
	MIT LICENSE
	Copyright (c) 2023, Mihail Szabolcs
*/
#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>
#include <stdint.h>
#include <math.h>

#ifndef IMAGE_WIDTH
	#define IMAGE_WIDTH 1024
#endif

#ifndef IMAGE_HEIGHT
	#define IMAGE_HEIGHT 1024
#endif

#define IMAGE_SIZE IMAGE_WIDTH * IMAGE_HEIGHT

typedef union
{
	uint32_t bgra;
	struct
	{
		uint8_t b : 8;
		uint8_t g : 8;
		uint8_t r : 8;
		uint8_t a : 8;
	};
} color_t;

#pragma pack(push, 1)
typedef struct {
   uint8_t idlength;
   uint8_t colourmaptype;
   uint8_t datatypecode;
   uint16_t colourmaporigin;
   uint16_t colourmaplength;
   uint8_t colourmapdepth;
   uint16_t x_origin;
   uint16_t y_origin;
   uint16_t width;
   uint16_t height;
   uint8_t bitsperpixel;
   uint8_t imagedescriptor;
} image_header_t;
#pragma pack(pop)

typedef struct
{
	int w;
	int h;
	int size;
	color_t *pixels;
	image_header_t header;
} image_t;

typedef struct
{
	float x, y;
} point_t;

typedef struct
{
	float x, y, z;
} vertex_t;

typedef struct
{
	int v0, v1, v2;
} triangle_t;

typedef struct
{
	vertex_t *vertices;
	triangle_t *triangles;
	int num_triangles;
} mesh_t;

static inline point_t vertex_to_point(
    const vertex_t vertex,
    const float half_w,
    const float half_h
);

static void image_clear(image_t *image, const color_t color);
static bool image_save(image_t *image, const char *filename);
static void image_render_line(
    image_t *image,
    const point_t p0,
    const point_t p1,
    const color_t color
);
static void image_render_mesh(
    image_t *image,
    const mesh_t *mesh,
    const color_t color
);

static image_t *image = &(image_t) {
	.w = IMAGE_WIDTH,
	.h = IMAGE_HEIGHT,
	.size = IMAGE_SIZE,
	.pixels = (color_t[IMAGE_SIZE]) { 0, },
	.header = {
		.datatypecode = 2,
		.width = IMAGE_WIDTH,
		.height = IMAGE_HEIGHT,
		.y_origin = IMAGE_HEIGHT,
		.bitsperpixel = sizeof(color_t) << 3,
		.imagedescriptor = 8 | (1 << 5)
	}
};

static const mesh_t *mesh = &(mesh_t) {
	#include "mesh.h"
};

int main(int argc, char *argv[])
{
	if(argc != 2)
	{
		fprintf(stderr, "usage: %s output.tga\n", argv[0]);
		return EXIT_FAILURE;
	}

	image_clear(image, (color_t) { .a = 0xFF });

	image_render_mesh(
        image,
        mesh,
        (color_t) {
            .r = 0xFF,
            .g = 0xFF,
            .a = 0xFF
        }
    );

	if(!image_save(image, argv[1]))
	{
		fprintf(stderr, "error: could not save image `%s'\n", argv[1]);
		return EXIT_FAILURE;
	}

	return EXIT_SUCCESS;
}

static inline point_t vertex_to_point(
    const vertex_t vertex,
    const float half_w,
    const float half_h
)
{
	return (point_t) {
		.x = vertex.x * half_w + half_w,
		.y = vertex.y * half_h + half_h
	};
}

static void image_clear(image_t *image, const color_t color)
{
	for(color_t *pixel = image->pixels,
        *end = image->pixels + image->size; pixel != end; pixel++)
		*pixel = color;
}

static bool image_save(image_t *image, const char *filename)
{
	FILE *fp = fopen(filename, "wb");
	if(fp == NULL)
		return false;

	if(fwrite(
        &image->header,
        1,
        sizeof(image->header), fp) != sizeof(image->header))
	{
		fclose(fp);
		return false;
	}

	if(fwrite(
        image->pixels,
        sizeof(color_t),
        image->size, fp) != image->size)
	{
		fclose(fp);
		return false;
	}

	fclose(fp);
	return true;
}

static void image_render_line(
	image_t *image,
	const point_t p0,
	const point_t p1,
	const color_t color
)
{
	color_t *pixels = image->pixels;
	const int w = image->w;

	const float x0 = p0.x;
	const float y0 = p0.y;
	const float dx = p1.x - x0;
	const float dy = p1.y - y0;

	const float step = 1.0f / fmaxf(fabsf(dx), fabsf(dy));

	for(float t = 0.0f; t < 1.0f; t += step)
	{
		const int x = x0 + dx * t;
		const int y = y0 + dy * t;

		pixels[x + y * w] = color;
	}
}

static void image_render_mesh(
    image_t *image,
    const mesh_t *mesh,
    const color_t color
)
{
	const vertex_t *vertices = mesh->vertices;
	const float half_w = image->w * 0.5f;
	const float half_h = image->h * 0.5f;

	for(const triangle_t *tri = mesh->triangles,
        *end = mesh->triangles + mesh->num_triangles; tri != end; tri++)
	{
		const vertex_t v0 = vertices[tri->v0];
		const vertex_t v1 = vertices[tri->v1];
		const vertex_t v2 = vertices[tri->v2];

		const point_t p0 = vertex_to_point(v0, half_w, half_h);
		const point_t p1 = vertex_to_point(v1, half_w, half_h);
		const point_t p2 = vertex_to_point(v2, half_w, half_h);

		image_render_line(image, p0, p1, color);
		image_render_line(image, p1, p2, color);
		image_render_line(image, p2, p0, color);
	}
}

/* vim: set ts=4 sw=4 sts=4 noet: */

suzanne

What a thing of beauty, isn’t it? Those lovely jaggies, just be hitting different. I think so too.

If we really wanted to do away with the fmax, fabs and the division, we could borrow the fast inverse square root implementation from Q3 and optimize things even further.

float Q_rsqrt(float number)
{
 	long i;
 	float x2, y;
 	const float threehalfs = 1.5F;

 	x2 = number * 0.5F;
 	y  = number;
 	i  = * ( long * ) &y; // evil floating point bit level hacking
 	i  = 0x5f3759df - ( i >> 1 ); // what the fuck?
 	y  = * ( float * ) &i;
 	y  = y * ( threehalfs - ( x2 * y * y ) ); // 1st iteration

 	return y;
}

void image_render_line(
	image_t *image,
	const point_t p0,
	const point_t p1,
	const color_t color
)
{
	color_t *pixels = image->pixels;
	const int w = image->w;

	const float x0 = p0.x;
	const float y0 = p0.y;
	const float dx = p1.x - x0;
	const float dy = p1.y - y0;

	const float step = Q_rsqrt(dx * dx + dy * dy);

	for(float t = 0.0f; t < 1.0f; t += step)
	{
		const int x = x0 + dx * t;
		const int y = y0 + dy * t;

		pixels[x + y * w] = color;
	}
}
https://mihail.co/2023/06/25/branchless-line-drawing/
2023 May Retrospective
Show full content
2023 May Retrospective

May was officially the proverbial “The Legend of Zelda: Tears of the Kingdom Month”, which got released and of course just like I anticipated it’s already my game of the year, without even a shadow of a doubt.

I only got to play it a week after the initial release, because I wanted to have a physical copy, just like I did when Breath of the Wild came out back in 2017, which feels like a lifetime ago now.

Take a look at a short (30 seconds) clip of me roaming around early in the game, so no spoilers, I promise.

I probably spent a good 4-5 hours, before I even considered leaving the starting area. It’s just so good.

As someone who put somewhere between 350 and 400 hours into Breath of the Wild, I can already tell that I will end up spending around the same if not more when it comes to Tears of the Kingdom.

In BoTW, I found all the shrines, maxed out health and stamina, found enough Koroks to unloock all inventory slots, completed all the DLC content except Trial of the Sword and some side-quests related to armor sets, also maxed out the ancient armor set, etc.

Still only got a completion percentage of about 60%, which shocked me to be honest.

Now, with all that laid out in the open and being 100% vulnerable on the information super-highway like that; starting ToTK with only 3 hearts, one stamina wheel, and no Revali’s Gale, it did feel a bit underwhelming at first, to say the least, but then it got better as I started uncovering the entire map, which was the first thing I did with BoTW as well.

I am still a bit on the fence when it comes about not having something akin to Revali’s Gale, and no I don’t consider Ascension to be a direct replacement; perhaps there will be something later into the game, but I haven’t gotten that far with the main quest line just yet in order to know.

The Zonai Devices are of course supposed to come in handy when it comes to replacing and even outdoing the old powers, since now you can easily just put some springs on top of each other and launch yourself into the air, or use fans and so forth, but considering that you need to farm Zonai charges in exchange for them, they do not feel like a direct replacement.

Another thing that I feel like it can be finicky is the radial menu, which I understand why it was done, but it just feels a bit clunky to use at first as well.

Other than these minor gripes, everything else just works and I can’t wait to explore every inch of the world and discover all there is to discover in the coming weeks and months.

What do I think about the item duplication bugs and the fact that Nintendo did end up patching some of them already, and probably patch more of them as people discovering them?

The only thing about I’ll say is that the game should (or probably will) get a “creative game mode” with infinite resources, so that one can just truly enjoy it as a sandbox without having to worry too much about farming resources, which can feel like a chore at times.

https://mihail.co/2023/05/31/2023-may-retrospective/
2023 April Retrospective
Show full content
2023 April Retrospective

April came and went like the wind, but I finally did end up posting about USD which can you peruse at your own leisure by clicking here.

Marmota

I rambled about Marmota in the past, but in case you were living under a rock and don’t know what it is, here’s a quick refresher.

Marmota is a tiny terminal emulator that I’ve cobbled together which uses [libvte][libvte] to do al the hard work when it comes to actual terminal emulation.

It’s been my daily driver ever since I released it and I ended up adding a couple of additional features to it in the months and years since its inception.

Some of these were simply quality of life type features, like the ability to zoom-in-and-out at runtime; while others were absolutely non-essential, like the ability to set a background image.

When it comes to configuration, all settings are compiled into the executable at compile time in traditional suckless.org style, however a couple of them can be overridden via the means of command line arguments.

Background Image

The -background and -background-opacity are one of these, which can come handy if you fancy setting the background and its opacity at runtime. Perhaps even in a randomized fashion, or whatever.

Images with abstract patterns and dark color palettes work best, because you don’t want to be stare at something that is too distracting or too light which makes the text difficult to read.

In addition I found that images which fade to black, from right to left, also seem to work fairly well.

Take this image of a chimpanzee from the article “Chimpanzee Rights Get a Day in Court” by Wired.

chimpanzee

Doing this by hand in your favorite Image Editor is probably going to get boring very fast, even if you do try to script it, so I ended up writing a small utility that can do this without too much hassle.

/*
	MIT LICENSE
	Copyright (c) 2023, Mihail Szabolcs
*/
#include <stdbool.h>
#include <stdint.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <memory.h>

#define STBI_NO_HDR
#define STBI_NO_LINEAR
#define STB_IMAGE_IMPLEMENTATION
#define STB_IMAGE_WRITE_IMPLEMENTATION
#define STB_IMAGE_RESIZE_IMPLEMENTATION
#include "stb_image.h"
#include "stb_image_write.h"
#include "stb_image_resize.h"

#ifndef UNUSED
	#define UNUSED(x) (void)(x)
#endif

#ifndef DEFAULT_OUTPUT_IMAGE_WIDTH
	#define DEFAULT_OUTPUT_IMAGE_WIDTH 1920
#endif

#ifndef DEFAULT_OUTPUT_IMAGE_HEIGHT
	#define DEFAULT_OUTPUT_IMAGE_HEIGHT 1080
#endif

typedef union
{
	uint32_t rgba;
	struct
	{
		uint32_t r : 8;
		uint32_t g : 8;
		uint32_t b : 8;
		uint32_t a : 8;
	};
} rgba_t;

#define resize_image(input, iw, ih, output, ow, oh) \
	stbir_resize_uint8( \
		(const unsigned char *) input, \
		iw, \
		ih, \
		0, \
		(unsigned char *) output, \
		ow, \
		oh, \
		0, \
		sizeof(rgba_t) \
	)

static void fill_image(
    rgba_t *image,
    const int w,
    const int h,
    const rgba_t color
);
static void flip_image(rgba_t *image, const int w, const int h);

static void process(
	const rgba_t *restrict input,
	rgba_t *restrict output,
	const int iw,
	const int ih,
	const int ow,
	const int oh
);

int main(int argc, char *argv[])
{
	int i, iw, ih, rw, rh, ow, oh, bpp, ret;
	bool flip_input;
	rgba_t *input, *rinput, *output;

	if(argc < 3 || argc > 6)
	{
		fprintf(
			stderr,
            "usage: %s input.png output.png [arguments...]\n\n",
            argv[0]
        );
		fprintf(stderr, "arguments:\n");
		fprintf(stderr, "    --flip   - flip input image horizontally\n");
		fprintf(stderr, "    --width  - set width of output image\n");
		fprintf(stderr, "    --height - set height of output image\n");
		return EXIT_FAILURE;
	}

	flip_input = false;
	ow = DEFAULT_OUTPUT_IMAGE_WIDTH;
	oh = DEFAULT_OUTPUT_IMAGE_HEIGHT;

	for(i = 3; i < argc; i++)
	{
		if(!strcmp(argv[i], "--flip"))
		{
			flip_input = true;
		}
		else if(!strcmp(argv[i], "--width") && i + 1 < argc)
		{
			ow = atoi(argv[++i]);
			if(ow <= 0)
			{
				fprintf(
                	stderr,
                    "error: invalid value for '--width' argument"
                );
				return EXIT_FAILURE;
			}
		}
		else if(!strcmp(argv[i], "--height") && i + 1 < argc)
		{
			oh = atoi(argv[++i]);
			if(oh <= 0)
			{
				fprintf(
                	stderr,
                    "error: invalid value for '--height' argument"
                );
				return EXIT_FAILURE;
			}
		}
		else
		{
			fprintf(stderr, "error: invalid argument '%s'\n", argv[i]);
			return EXIT_FAILURE;
		}
	}

	input = (rgba_t *) stbi_load(argv[1], &iw, &ih, &bpp, sizeof(rgba_t));
	if(input == NULL)
	{
		fprintf(
            stderr,
            "failed to load input image: %s\n",
            stbi_failure_reason()
        );
		return EXIT_FAILURE;
	}

	rw = ow;
	rh = oh;

	if(ih > 0 && ih != rh)
	{
		rw = iw * ((double) rh / (double) ih);

		rinput = malloc(rw * rh * sizeof(rgba_t));
		if(rinput == NULL)
		{
			fprintf(
                stderr,
                "failed to allocate memory to resize input image\n"
            );
			free(input);
			return EXIT_FAILURE;
		}

		if(!resize_image(input, iw, ih, rinput, rw, rh))
		{
			fprintf(
                stderr,
                "failed to resize input image from %dx%d to %dx%d ...\n",
                iw,
                ih,
                rw,
                rh
            );
			free(rinput);
			free(input);
			return EXIT_FAILURE;
		}
        
        free(input);

		fprintf(
            stderr,
            "resized input image from %dx%d to %dx%d ...\n",
            iw,
            ih,
            rw,
            rh
        );

		input = rinput;
		iw = rw;
		ih = rh;
	}

	output = malloc(ow * oh * sizeof(rgba_t));
	if(output == NULL)
	{
		fprintf(stderr, "failed to allocate memory for output image\n");
		free(input);
		return EXIT_FAILURE;
	}

	fill_image(output, ow, oh, (rgba_t) { .a = 0xFF });

	if(flip_input)
	{
		flip_image(input, iw, ih);
		fprintf(stderr, "flipped input image horizontally ...\n");
	}

	process(input, output, iw, ih, ow, oh);

	ret = stbi_write_png(
        argv[2],
        ow,
        oh,
        sizeof(rgba_t),
        output,
        ow * sizeof(rgba_t)
    );
	if(ret == 0)
	{
		fprintf(stderr, "failed to write output image: '%s'\n", argv[2]);
		ret = EXIT_FAILURE;
	}
	else
	{
		fprintf(stdout, "wrote output image to '%s' ...\n", argv[2]);
		ret = EXIT_SUCCESS;
	}

	free(output);
	free(input);
	return ret;
}

static void fill_image(
    rgba_t *image,
    const int w,
    const int h,
    const rgba_t color
)
{
	for(rgba_t *c = image, *e = image + w * h; c != e; c++)
		*c = color;
}

static void flip_image(rgba_t *image, const int w, const int h)
{
	int x, y, ww, xw, yo, i, j;
	rgba_t c;

	ww = w - 1;
	xw = w >> 1;

	for(y = 0; y < h; y++)
	{
		yo = y * w;

		for(x = 0; x < xw; x++)
		{
			i = x + yo;
			j = (ww - x) + yo;

			c = image[i];
			image[i] = image[j];
			image[j] = c;
		}
	}
}

static void process(
	const rgba_t *restrict input,
	rgba_t *restrict output,
	const int iw,
	const int ih,
	const int ow,
	const int oh
)
{
	int x, y, ww, xi, xo;
	double t, tw;
	rgba_t c;

	UNUSED(oh);

	xi = 0;
	ww = iw;

	if(iw > ow)
	{
		xi = iw - ow;
		ww = ow;
		input = input + xi;
	}

	xo = ow - ww;
	output = output + xo;

	tw = 1.0 / iw;

	for(y = 0; y < ih; y++)
	{
		for(x = 0; x < ww; x++)
		{
			t = x * tw;

			c = *input++;

			c.r = c.r * t;
			c.g = c.g * t;
			c.b = c.b * t;

			*output++ = c;
		}

		input += xi;
		output += xo;
	}
}

/* vim: set ts=4 sw=4 sts=4 noet: */

In order to compile this, you’ll need to put stb_image.h, stb_image_write.h and stb_image_resize.h in the current directory and then:

$ cc -O2 wallpaper.c -o wallpaper
$ ./wallpaper chimpanzee.png wallpaper.png --flip

chimpanzee

Voila! Not too bad, right?

chimpanzeet

Background Video

Background images are cool, but have you ever used background videos?

Background videos have been a pet peeve of mine for quite some time and I wasn’t entirely sure if redrawing the background 24+ times per second would be a realistic thing to do with [libvte][libvte].

I finally decided to go ahead and give it a try and use this as the perfect excuse to try out the excellent single file pl_mpeg library by Dominic Szablewski to decode MPEG videos.

Yes, you read it right, it’s MPEG. There’s no way that I would pull in some crazy dependency like gstreamer just for the sake of such an experimental feature.

It should go without saying that the video should not exceed 10-15 seconds in length and should loop in a seamless manner for the best experience.

In addition it might worth experimenting and downscale the video to 720p and then have it upscaled automatically by specifying the -background-auto-scale command line argument.

The other considerations around patterns and color palettes that I touched upon when I talked about background images should apply for videos as well.

To make the background video seen above, I extracted around 10 seconds from Sands Of Arrakis.

The effect is pretty subtle, but one can clearly notice the sandstorm moving from left to right in the background.

Finally, here’s the incantation to convert an MP4 video into a compatible MPEG video without sound.

$ ffmpeg -i input.mp4 -c:v mpeg1video -q:v 0 -an -format mpeg output.mpg
The Legend of Zelda: Tears of the Kingdom

Here we go again, it’s that time of the month again. As the release date is only roughly 2 weeks away now, Nintendo has been dropping some more trailers as well as lifting the embargo, so that the select few can finally share their hands-on experience with playing the game for a whopping hour or so.

Let’s start things off with the final official pre-launch trailer (#3).

As a follow-up, there is the “Dive Into the Unknown” mini-trailer.

Then, there is an actual ad titled “You can do what?”; This is really a tie-in for the NIntendo Switch console more than anything else.

Finally, a curated list of videos by people who got an early sneak peek into the game during last week.

If you watch the videos, you’ll notice how similar they are, which in turn illustrates how much control Nintendo actually has over what is shared, including in what form and when. It’s pretty impressive really.

https://mihail.co/2023/04/30/2023-april-retrospective/
Universal Scene Description
Show full content
Universal Scene Description

I’ve been promising to write about USD (Universal Scene Description) for quite some time now, and the day has finally come.

If there ever was some top ten of worst file formats known to mankind somewhere, USD would end up ranking pretty high on that list. Perhaps even in the illustrious top three.

The unfortunate thing is that Apple decided to adopt USD as a defacto format and integrated it into its toolchains and pipelines. This in turn means that it’s impossible to avoid having to deal with it if one is making anything that is remotely portable and cross platform.

I mean it’s not an absolute mystery as to why this happened. I’ll give you a hint: Apple and Pixar. Figured it out already? I’ll give you 5 more minutes on that one.

You can find a presentation by clicking here from back in 2019. The accompanying slides of said presentation can be found over here.

If you awe a gander at the slides, you might notice this little nugget of absolute pure gold, which I have the pleasure of presenting you below:

Focus on speed, scalability and collaboration.

I’ll let you be the judge if any of it ends up ringing true by the end of this post.

Anyway, before I dive into the nitty-gritty details, I better give you a quick overview of why I ended up being involved with USD in the first place, in other words why do even I care?

Some co-workers of mine were working on a project that needed the ability to export a model into USD at runtime, for the purposes of previewing said model in an Augment Reality setting.

After taking a cursory look at the documentation, I said, well how hard can it be? I’ve done this several times before, writing an exporter is not rocket science by any stretch of the imagination.

Of course, there were some constraints; namely:

  • MUST be written in C#
  • MUST work as a Unity Script / Plug-in
  • MUST work in the Unity Web Player
  • MUST also work as a standalone command line executable (without compiling in the Unity specific bits); iterating “inside” Unity is an absolute nightmare, it’s ridiculously slow regardless of the hardware you tend to have; how do people use it on a day-to-day basis it’s beyond me, but that’s a discussion for another fine day
  • MUST not have any third party dependencies of any kind

Nothing too unreasonable so far, and I hope that you do agree with me.

So, this became one of my side projects for a while and I started digging into the documentation and laying the down some of the foundations.

What I found is that in reality USD is composed of three formats, namely: USDZ, USDA and USDC.

USDZ package

The USDZ package is a container format that contains one or more USDA/USDC files, plus any other resources referenced by them; like textures for example.

As you might have guessed, the Z stands for ZIP and USDZ is nothing more than a glorified ZIP archive with another extension, which is not that unusual or bad for that matter, but as you might have already guessed there were some gotchas that I wasn’t aware of at first, which I’ll touch on later.

Nonetheless, this meant that I had to come up with a way of creating ZIP archives without any dependencies.

CRC32

While going about this, I’d like to call out a few things, one of them being calculating a compatible CRC32 checksums for each ZIP file entry.

I’ve done this by adapting the code from [bsd/libkern/crc32.c][crc32]. If you do look around online for other implementations they all seem to go into crazy-land for some reason. The simplicity of this implementation is an absolute godsend.

using System;
using System.Text;

// https://opensource.apple.com/source/xnu/xnu-1456.1.26/bsd/libkern/crc32.c
class CRC32
{   
	internal static readonly uint[] crc32 =
	{   
		0x00000000,
        /* full array ommited for purposes of brevity */
    };
    
    public static uint Compute(byte[] bytes)
    {   
    	uint crc = ~0U;

        for(int i = 0; i < bytes.Length; i++)
        	crc = crc32[(crc ^ bytes[i]) & 0xff] ^ (crc >> 8); 

        return crc ^ ~0U;
    }   

    public static uint Compute(string s)
    {   
		return Compute(Encoding.UTF8.GetBytes(s));
    }
}

Console.WriteLine(CRC32.Compute("hello world"));
DOS Compatible Timestamp

Another thing that came up was the need to compute DOS compatible timestamps, which I thought would be worth sharing, just in case somebody is in desperate need of it for some reason or another all of the sudden.

DateTime dt = DateTime.Now;

uint date = (uint) (((dt.Year - 1980) << 9) | (dt.Month << 5) | dt.Day);
uint time = (uint) ((dt.Hour << 11) | (dt.Minute << 5) | (dt.Second >> 1));

Do not get too discouraged by all the bit-shifting magic in there.

Alignment

Nothing too crazy so far, right? Well, this is where the madness begins, so let’s get the party going with an actual excerpt from the official documentation:

The only absolute layout requirement a usdz package makes of files within the package is that the data for each file begin at a multiple of 64 bytes from the beginning of the package.

Wait what? Exactly, that was my reaction as well. But is this is even possible?

If we take a look at ZIP specification, we can see that each “file entry” within the ZIP archive has a so called extra field length; which denotes the length in bytes of any “extra metadata” one might want to associate and store with said file entry. Any unzip tool that doesn’t care about this extra metadata can simply ignore it without having to worry about it, thus eliminating any compatibility problems.

In other words, we can (ab)use this in order to satisfy the alignment requirements by calculating the extra field length in order to align the actual data of the file entry on a 64 byte boundary starting from the beginning of the ZIP archive.

uint ExtraFieldLength = 64 - ((Offset + Filename.Length + 34) & 63);

The magic number 34 is simply the size in bytes of the ZIP file entry header.

Just swell, right? Absolutely. Oh, don’t you worry, the madness doesn’t stop just yet, or at least not until the morale improves.

It is also worth mentioning that none of the file entries within the ZIP archive are actually compressed at all; they are simply STORED uncompressed, defeating the entire purpose of leveraging a ZIP archive in the first place.

USDA

Now that I had the means to create arbitrary ZIP archives without much fuss, it was time to dig into USDA. which is a text based file format. The A in the name stands for ASCII as you might have guessed.

Quite verbose, which already made me think why on earth would you want to store a text based file format uncompressed within a ZIP archive?

It truly boggles the mind, but it is what it is. It’s too late to put the proverbial toothpaste back into the tube, I guess.

Take a look at a sample USDA file below which describes a simple textured cube.

#usda 1.0
(
	customLayerData = {
		string creator = "Creator"
	}
	metersPerUnit = 1
	upAxis = "Y"
	defaultPrim = "model"
)

def Xform "model" (
	assetInfo = {
		asset identifier = @cube.usda@
		string name = "model"
	}
	kind = "component"
)
{
	matrix4d xformOp:transform = (
		(1, 0, 0, 0), (0, 1, 0, 0), (0, 0, 1, 0), (0, 0, 0, 1)
	)
	uniform token[] xformOpOrder = ["xformOp:transform"]

	def Mesh "mesh"
	{
		int[] faceVertexCounts = [3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3]
		int[] faceVertexIndices = [
			0, 2, 1, 2, 3, 1, 4, 6, 5, 6, 7, 5, 8, 10, 9, 10, 11, 9,
			12, 14, 13, 14, 15, 13, 16, 18, 17, 18, 19, 17, 20, 22,
			21, 22, 23, 21
		]

		normal3f[] normals = [
			(1, 0, 0), (1, 0, 0), (1, 0, 0), 
			(1, 0, 0), (-1, 0, 0), (-1, 0, 0), 
			(-1, 0, 0), (-1, 0, 0), (0, 1, 0), 
			(0, 1, 0), (0, 1, 0), (0, 1, 0), 
			(0, -1, 0), (0, -1, 0), (0, -1, 0), 
			(0, -1, 0), (0, 0, 1), (0, 0, 1),
			(0, 0, 1), (0, 0, 1), (0, 0, -1), 
			(0, 0, -1), (0, 0, -1), (0, 0, -1)
		] (
			interpolation = "vertex"
		)
		point3f[] points = [
			(0.5, 0.5, 0.5), (0.5, 0.5, -0.5), (0.5, -0.5, 0.5),
			(0.5, -0.5, -0.5), (-0.5, 0.5, -0.5), (-0.5, 0.5, 0.5),
			(-0.5, -0.5, -0.5), (-0.5, -0.5, 0.5), (-0.5, 0.5, -0.5),
			(0.5, 0.5, -0.5), (-0.5, 0.5, 0.5), (0.5, 0.5, 0.5),
			(-0.5, -0.5, 0.5), (0.5, -0.5, 0.5), (-0.5, -0.5, -0.5), 
			(0.5, -0.5, -0.5), (-0.5, 0.5, 0.5), (0.5, 0.5, 0.5), 
			(-0.5, -0.5, 0.5), (0.5, -0.5, 0.5), (0.5, 0.5, -0.5), 
			(-0.5, 0.5, -0.5), (0.5, -0.5, -0.5), (-0.5, -0.5, -0.5)
        ]
		float2[] primvars:st = [
			(0, 0), (1, 0),
			(0, 1), (1, 1),
			(0, 0), (1, 0),
			(0, 1), (1, 1),
			(0, 0), (1, 0),
			(0, 1), (1, 1),
			(0, 0), (1, 0),
			(0, 1), (1, 1),
			(0, 0), (1, 0),
			(0, 1), (1, 1),
			(0, 0), (1, 0),
			(0, 1), (1, 1)
		] (
			interpolation = "vertex"
		)

		uniform token subdivisionScheme = "none"
		rel material:binding = </model/material>
	}

	def Material "material"
	{
		token inputs:frame:stPrimvarName = "st"
		token outputs:surface.connect = 
</model/material/PreviewSurface.outputs:surface>

		def Shader "PreviewSurface"
		{
			uniform token info:id = "UsdPreviewSurface"
			color3f inputs:diffuseColor.connect =
</model/material/Diffuse.outputs:rgb>
			token outputs:surface
		}

		def Shader "Primvar"
		{
			uniform token info:id = "UsdPrimvarReader_float2"
			float2 inputs:default = (0, 0)
			token inputs:varname.connect =
</model/material.inputs:frame:stPrimvarName>
			float2 outputs:result
		}

		def Shader "Diffuse"
		{
			uniform token info:id = "UsdUVTexture"
			float4 inputs:default = (1, 1, 1, 1)
			asset inputs:file = @textures/diffuse.png@
			float2 inputs:st.connect = 
</model/material/Primvar.outputs:result>
			token inputs:wrapS = "repeat"
			token inputs:wrapT = "repeat"
			float3 outputs:rgb
		}
	}
}

And, down below you can awe a gander at how does it actually look like when previewed with usdview, which is a utility part of the official toolkit, which also happens to contain the reference implementation of the USD writer and reader.

This text based format is documented to a certain extent and as a result it wasn’t too hard to spit out some vertices, indices, materials and so forth.

It should go without saying that the entire format has been tailored towards a PBR workflow, which is understandable, but can be confusing at first, especially when it comes to topics like “Roughness vs Glossiness”.

No matter how you slice or dice it a text based file format will always be verbose, however not all text based file formats are equal.

USDA has a lot of identifiers (keywords) and many of them aren’t very self explanatory. What even is an “Xform”? Don’t answer that, since this was a retorical question.

Another thing that is problematic is the representation of arrays. Let’s take a look at an example below.

int[] faceVertexCounts = [3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3]

While it is nice that we get to know the type and the fact that it’s an array without having to peek at the next token while parsing, the fact that we don’t know the number of elements in the array makes parsing arrays not very performant, since one will have to continuously grow the array, which doesn’t make any sense to me, especially when this format was supposed to be performant to parse and consume.

This could be improved very easily and singnificantly with a tiny change to the syntax.

 int[12] faceVertexCounts = [3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3]

With this change, one simply parses out the size of the array and allocates enough memory for the entire array at once, then the actual elements can be parsed without any complications, simplifying and speeding up the entire process.

But, wouldn’t this complicate matters when it comes to writing? Not really, since you already know the number of items in any arrays that you will spit out.

Alas, in the real world you’d have to deal with way arrays that contain way more than 12 elements and without the ability to know how many in adance, your only feasable option is to just grow the array twice to its previous size (or similar) and hope for the best, in order to try to minimize the number of allocations. Which is not ideal, but it’s more and better than nothing.

You might be tempted to ask by this point if maybe a binary format would have been better, right? And you would be absolutely justified to ask that question, to which the answer is quite simple in the sense that there indeed is a binary equivalent to USDA in the form of USDC.

USDC

Now, along the way, I am pretty sure that the Vice President of Bad Ideas, might have realized that storing a text based file format in a ZIP archive uncompressed is not exactly the brightest of ideas for a number of not so hard to guess reasons, and decided to send out an email at 4:20PM on a lovely Sunday afternoon, with a single word in it that said:

thoughts?

– vp of bad ideas

And, this is how the so called binary crate format (USDC) was born (please don’t quote me on this!); which of course has support for compressed int and float arrays. Absolutely shocking, I know!

Such hardcore and revolutionary thinking. How could anyone, but a true genius and Renaissance Man come up with such an absolutely brilliant idea?

It is the most tragicomic thing I’ve seen in my entire life, and I’ve seen some, believe you me.

As one might expect, the binary crate format is not documented at all; or at least I wasn’t able to find anything and my google-fu is pretty good still; which means that the only way to learn more and untangle the format is to look directly at the open source reference implementation.

And, this is where I kind of put the project on the shelf for an elf, because I had absolutely zero motivation to try to understand and decipher it.

It’s one of the worst and most convoluted pieces of code I’ve ever seen. I am saying this with no ill feelings at all, because I’ve written crappy code in my life as much as the next person, but never ever went this far down the proverbial rabbit hole expecting to bump into mighty Neo himself along the way.

Before you start frantically searching for me on social media and call me a pretentious bastard, let me give you a tiny taste, by showing you a small excerpt below. And, if you still feel the same after you’ve seen it, then feel free to curse and yell at me like a true gentleman and scholar would on any respectable social media platform.

template <class Writer, class T>
static inline
typename std::enable_if<
    std::is_same<T, GfHalf>::value ||
    std::is_same<T, float>::value ||
    std::is_same<T, double>::value,
    ValueRep>::type
_WritePossiblyCompressedArray(
    Writer w, VtArray<T> const &array, CrateFile::Version ver, int)
{
    // Version 0.6.0 introduced compressed floating point arrays.
    if(ver < CrateFile::Version(0,6,0) ||
        array.size() < MinCompressedArraySize)
    {
        return _WriteUncompressedArray(w, array, ver);
    }

    // Check to see if all the floats are exactly represented as integers.
    auto isIntegral = [](T fp)
    {
        constexpr int32_t max = std::numeric_limits<int32_t>::max();
        constexpr int32_t min = std::numeric_limits<int32_t>::lowest();
        return min <= fp && fp <= max &&
            static_cast<T>(static_cast<int32_t>(fp)) == fp;
    };    
    if(std::all_of(array.cdata(), array.cdata() + array.size(), isIntegral))
    {
        // Encode as integers.
        auto result = ValueRepForArray<T>(w.Tell());
        (ver < CrateFile::Version(0,7,0)) ?
            w.template WriteAs<uint32_t>(array.size()) :
            w.template WriteAs<uint64_t>(array.size());
        result.SetIsCompressed();
        vector<int32_t> ints(array.size());
        std::copy(array.cdata(), array.cdata() + array.size(), ints.data());
        // Lowercase 'i' code indicates that the floats are written as
        // compressed ints.
        w.template WriteAs<int8_t>('i');
        _WriteCompressedInts(w, ints.data(), ints.size());
        return result;
    }
    
    // Otherwise check if there are a small number of distinct values, which we
    // can then write as a lookup table and indexes into that table.
    vector<T> lut;
    // Ensure that we give up soon enough if it doesn't seem like building a
    // lookup table will be profitable.  Check the first 1024 elements at most.
    unsigned int maxLutSize = std::min<size_t>(array.size() / 4, 1024);
    vector<uint32_t> indexes;
    for(auto elem: array)
    {
        auto iter = std::find(lut.begin(), lut.end(), elem);
        uint32_t index = iter-lut.begin();
        indexes.push_back(index);
        if(index == lut.size())
        {
            if(lut.size() != maxLutSize)
            {
                lut.push_back(elem);
            }
            else
            {
                lut.clear();
                indexes.clear();
                break;
            }
        }
    }
    if(!lut.empty())
    {
        // Use the lookup table.  Lowercase 't' code indicates that
        // floats are written with a lookup table and indexes.
        auto result = ValueRepForArray<T>(w.Tell());
        (ver < CrateFile::Version(0,7,0)) ?
            w.template WriteAs<uint32_t>(array.size()) :
            w.template WriteAs<uint64_t>(array.size());
        result.SetIsCompressed();
        w.template WriteAs<int8_t>('t');
        // Write the lookup table itself.
        w.template WriteAs<uint32_t>(lut.size());
        w.WriteContiguous(lut.data(), lut.size());
        // Now write indexes.
        _WriteCompressedInts(w, indexes.data(), indexes.size());
        return result;
    }

    // Otherwise, just write uncompressed floats.  We don't need to write
    // a code byte here like the 'i' and 't' above since the resulting 
    // ValueRep is not marked compressed -- the reader code will thus just
    // read the uncompressed values directly.
    return _WriteUncompressedArray(w, array, ver);
}

Now, I don’t know about you, but as for myself, I’d very much like to absolutely not have any code written like this in any codebase near me. Not today and not ever, and I would very much like to avoid having to work with anybody who has ever had the pleasure to write code in this manner, willingly or unwillingly.

No, I didn’t purposely pick one particular snippet (or area) in order to try to make my case. I truly wish that was the case for everyone’s sake.

To make matters more interesting, on mobile one does need USDC for obvious performance related reasons, so there’s no way to avoid having to deal with the binary create format, no matter how you slice or dice it.

The Reference Implementation

Yet another thing that you might be tempted to ask is why not just use the reference implementation as-is and problem solved, right? See no evil, hear no evil? Anyone?

First and foremost, it’s an absolute nightmare to build. Anything written in so called modern C++ is prone to severe code-rot. Even today, when compilers generally support the same feature set, it can still happen that all of the sudden a newer compiler version considers a specific thing an error, while it might have been previously valid (albeit resulting in undefined behavior) for ages.

When that happens, one has to go and dig into it and do ad-hoc fixes, which most of the time you cannot contribute back until the project itself officially bumps the supported C++ and/or compiler version, it’s just not something one would want to waste their time with. Unless of course, one got unlimited time, which I sadly don’t.

Trust me, you don’t want to be in the position of having to debug over half a million lines of modern C++ that is made worse by the absolute obscene amounts of STL sprinkled in all over the place, without any consideration at all, nor any concern when it comes to readability or performance.

I mean, just look again at code-snippet above, it should be enough to convince you to never want to touch it with a ten foot pole.

If the reference implementation would have been the bona-fide experimental passion project of someone hacking away at it in their sparetime, then I would have been more understanding and would have more than likely cut them some slack. However, this is not the case at all, and the people who produced this monstrosity should have known better in the first place.

Once again, there’s a difference in terms of expectations between something experimental and something that claims to be a “high-performance extensible software platform for collaboratively constructing animated 3D scenes, designed to meet the needs of large-scale film and visual effects production”.

Final thoughts

Avoid USD at all costs if you can. It’s not the holy grail of exchange formats that it claims to be. While attractive and well thought out on the surface, once you dig in, the cracks start to show pretty fast and it’s all downhill from there.

If you must use it, avoid the reference implementation and implement only the subset of features that you need to read/write. Infesting your asset pipeline with the reference implementation is a sure way to dig yourself and your tooling into a hole that will be almost impossible to get out of.

For an AR use-case like mine, where we are talking about a relatively low poly simple mesh with a material, it can work; but if you are exporting entires scenes, like a “level” (or chunk of a level) in an AAA grade game type scenario, then I would say that you are setting up yourself for a lot of trouble down the line.

Any format that covets itself to be some sort of an “interchange format”, in my honest opinion it should satisfy the following conditions and/or requirements:

  1. MUST be easy to write/read (parse)
  2. MUST be easy to extend
  3. MUST be able handle large “scenes”
  4. MUST not be built around a particular workflow (i.e PBR)
  5. MUST have a dependency free and sane reference implementation

Sadly, USD falls shorts in one way or another when it comes to almost all of these.

https://mihail.co/2023/04/29/universal-scene-description/
2023 March Retrospective
Show full content
2023 March Retrospective

March is over and I still haven’t gotten around refining my post about USD, but do intend to do that in the next few days given that all goes well. Trying real hard not to make it too contentious or critical, which is an easier thing said than done all things considered.

Other than that, I didn’t get to focus on anything else on my long list of items that I keep in my giant back-burner. I truly hope that April will be a whole lot less of a hectic month and I’ll have something even remotely interesting to share.

The Legend of Zelda: Tears of the Kingdom

It looks like I ended up posting about Tears of the Kingdom in both January and February. It might be become a permanent column in these monthly retrospectives; or the very least until the launch day, which is not very far away at this point, and seriously can’t come soon enough.

You can find the newest whopping 13-minute-long gameplay trailer below, presented by none other than Eiji Aonuma himself, who happens to be the producer of the game.

There are a lot of naysayers of course, many among them with their usual complaints about weapon durability and the whole thing being just a glorified DLC selling for a whopping $70 and so forth.

But, all of them will buy the game anyway, they just like to play the role of a glorious keyboard warrior in the various comment sections across the information super highway.

If you happen to be one of those people who doesn’t like or want any spoilers at all before release, then of course please resist all temptations by the devil and avoid clicking on the play button above.

https://mihail.co/2023/03/31/2023-march-retrospective/
2023 February Retrospective
Show full content
2023 February Retrospective

February came and went as well. I did manage to write about creating normal maps from diffuse maps, which you can read by clicking here. The actual results turned out to be way better than I originally expected, considering that the entire process is not super complicated.

As always, I like to jump on the hype-train when new and shiny dank memes and such are being born, so I created a small bookmarklet that you can use to play snake on your GitHub contributions matrix.

snake

If you fancy trying it out, you can find the instructions by heading over here.

In other news, we got a brand new trailer for The Legend of Zelda: Tears of the Kingdom this month; which of course has been analyzed to death by everybody and their grandmother called Zelda.

https://mihail.co/2023/02/28/2023-february-retrospective/
Creating normal maps from diffuse maps
Show full content
Creating normal maps from diffuse maps

When I looked around the inter-webs to see how people go about doing this, pretty much everybody and their grandmother was talking about using a Sobel filter as the one and only true way.

An apparent general consensus about any subject, will always make me suspicious and curious about exploring alternative ways of doing said thing.

Before we get started, let’s pick a suitable test subject in the form of a rather lovely and hand-painted 512x512 stone floor texture from opengameart.org.

img

The first order of business is to turn the diffuse map into a height-map by converting every pixel into its gray-scale equivalent.

/*
	MIT LICENSE
	Copyright (c) 2023, Mihail Szabolcs
*/
typedef union
{
    uint32_t opaque;
    struct
    {
        uint32_t r : 8;
       	uint32_t g : 8;
        uint32_t b : 8;
        uint32_t a : 8;
    };
} rgba_t;

void build_normal_map(const rgba_t *input, rgba_t *output, int w, int h)
{
    int x, y;
    uint32_t gray;
    rgba_t c;

    for(y = 0; y < h; y++)
    {
        for(x = 0; x < w; x++)
        {
            c = input[x + y * w];
            gray = (c.r + c.g + c.b) / 3.0f;

            c.r = gray;
            c.g = gray;
            c.b = gray;

            output[x + y * w] = c;
        }
    }
}

We do this by calculating the average of the r, g and b components of every pixel, which results in an image like the one presented below. Nothing too fancy or ground breaking.

output

Of course that we could use some other formula to convert to gray-scale, rather than just taking the average, but this is more than good enough for our purposes.

Now it is time to turn the height-map into a normal-map (bump map) by calculating the change in height for every pixel in the input height-map and storing the result in the output normal map.

To do this, we simply sample 4 pixels around the current pixel, calculate the difference on the x and y axis respectively and then “normalize” the value from the [-1, 1] range to [0, 1] and then finally [0, 255] range.

We also invert the green channel, which holds the value of the Y axis, because we intend to use the normal map with OpenGL. In case of DirectX, this is [unnecessary][invertgreenchannel]. Obviously, this could also be inverted at run-time when the normal map is loaded or in a shader.

/*
	MIT LICENSE
	Copyright (c) 2023, Mihail Szabolcs
*/
#define minf(a, b) ((a) < (b) ? (a) : (b))
#define maxf(a, b) ((a) > (b) ? (a) : (b))
#define grayscalef(c) ((c.r + c.g + c.b) / 3.0f)

void build_normal_map(const rgba_t *input, rgba_t *output, int w, int h)
{
    int x, y;
    float gu, gd, gl, gr, dx, dy;
    rgba_t c;

    for(y = 0; y < h; y++)
    {
        for(x = 0; x < w; x++)
        {
            gu = grayscalef(input[x + maxf(y - 1, 0) * w]);
            gd = grayscalef(input[x + minf(y + 1, h - 1) * w]);
            gl = grayscalef(input[maxf(x - 1, 0) + y * w]);
            gr = grayscalef(input[minf(x + 1, w - 1) + y * w]);

            dx = (gl - gr) / 255.0f * 0.5f + 0.5f;
            dy = (gu - gd) / 255.0f * 0.5f + 0.5f;

            c = input[x + y * w];
            c.r = dx * 255;
            c.g = 255 - dy * 255;
            c.b = 255;

            output[x + y * w] = c;
        }
    }
}

The result is a little bit anemic to say the least, but it’s still possible to see some of the bevels if one squints just the right way. At any rate, this is not very promising at all, but it’s more than nothing.

output

Let’s take a look at what we could do in order to improve this situation. If we load up the resulting normal map in GIMP and then play around with the “Curves” tool, we can notice that an S-shaped curve seems to help quite bit.

s-curve-gimp

We could just use a Bezier curve with 2 control points and interpolate to achieve a similar curve, but there’s another way by making good use of a so called sigmoid curve.

Let’s head over to the desmos graphing calculator and plot this very cute sigmoid curve.

scurve

The -33 is simply a magic number that is good enough and was chosen purely arbitrarily. If we were to turn this into an actual tool, then we would probably also want to make this a user configurable as it can affect the output in various ways.

We also shift the curve to the right by 0.5 hence the x - 0.5.

/*
	MIT LICENSE
	Copyright (c) 2023, Mihail Szabolcs
*/
void build_normal_map(const rgba_t *input, rgba_t *output, int w, int h)
{
    int x, y;
    float gu, gd, gl, gr, dx, dy;
    rgba_t c;

    for(y = 0; y < h; y++)
    {
        for(x = 0; x < w; x++)
        {
            gu = grayscalef(input[x + maxf(y - 1, 0) * w]);
            gd = grayscalef(input[x + minf(y + 1, h - 1) * w]);
            gl = grayscalef(input[maxf(x - 1, 0) + y * w]);
            gr = grayscalef(input[minf(x + 1, w - 1) + y * w]);

            dx = (gl - gr) / 255.0f * 0.5f + 0.5f;
            dy = (gu - gd) / 255.0f * 0.5f + 0.5f;

            dx = 1.0f / (1.0f + exp(-33 * (dx - 0.5f)));
            dy = 1.0f / (1.0f + exp(-33 * (dy - 0.5f)));

            c = input[x + y * w];
            c.r = dx * 255;
            c.g = 255 - dy * 255;
            c.b = 255;

            output[x + y * w] = c;
        }
    }
}

output-final

Quite an improvement compared to what we got before, with many of the finer details now coming through as one would expect. Now let’s do some optimizations before we wrap this all up and call it a day.

It turns out that we can do away with most of the divisions by multiplying with the inverse and pre-calculate some of the index offsets on the y axis which lets us avoid some of the useless multiplications that we are currently doing when calculating the actual index into the input and the output arrays.

/*
	MIT LICENSE
	Copyright (c) 2023, Mihail Szabolcs
*/
#define GRAYSCALE_INV (1.0f / (3.0f * 255.0f))
#define grayscalef(c) ((c.r + c.g + c.b) * GRAYSCALE_INV)

void build_normal_map(const rgba_t *input, rgba_t *output, int w, int h)
{
    int x, y, ww, hh, yo, you, yod;
    float gu, gd, gl, gr, dx, dy;
    rgba_t c;

    ww = w - 1;
    hh = h - 1;

    for(y = 0; y < h; y++)
    {
       	yo = y * w;
        you = maxf(y - 1, 0) * w;
        yod = minf(y + 1, hh) * w;

        for(x = 0; x < w; x++)
        {
            gu = grayscalef(input[x + you]);
            gd = grayscalef(input[x + yod]);
            gl = grayscalef(input[maxf(x - 1, 0) + yo]);
            gr = grayscalef(input[minf(x + 1, ww) + yo]);

            dx = (gl - gr) * 0.5f + 0.5f;
            dy = (gu - gd) * 0.5f + 0.5f;

            dx = 1.0f / (1.0f + exp(-33 * (dx - 0.5f)));
            dy = 1.0f / (1.0f + exp(-33 * (dy - 0.5f)));

            c = input[x + yo];
            c.r = dx * 255;
            c.g = 255 - dy * 255;
            c.b = 255;

            output[x + yo] = c;
        }
    }
}

We could have also gotten rid of the clamping if we wanted to by simply doing a bitwise modulo on the x and y variables; this assumes that the width and height are power of two in which case we know that the following is true:

x & (w - 1) = x % w
y & (h - 1) = y % h

Yet another thing we could have done is to add the restrict qualifier to the input and output arguments in order to give an extra hint to the compiler that input and output arrays never actually overlap.

static void build_normal_map(
    const rgba_t *restrict input,
    rgba_t *restrict output,
    int w,
    int h
)

If your C compiler of choice supports C11, then restrict should be available, if not, then it might still be available under a different name like __restrict.

And now, let us take a final look at our handy work by checking out the resulting normal map in action with one rotating light source.

Not too shabby, right? I think so too as well.

https://mihail.co/2023/02/07/creating-normal-maps/
2023 January Retrospective
Show full content
2023 January Retrospective

January as usual is the so called trial-month of the year, is all said and done already. It truly flew by and as usual I didn’t get all that much done. Which is to be expected.

I do have two posts in the oven coming up in February if all goes well. One about creating normal maps from diffuse maps and another about USD, which I have been promising to write about for quite some time now, but never really found the time to do so until now.

One of my semi-unofficial new years resolutions was to write more in addition to my regular monthly retrospectives, which I intend to keep, albeit not religiously of course.

In other news, May can’t come soon enough for The Legend of Zelda: Tears of the Kingdom to drop.

https://mihail.co/2023/01/31/2023-january-retrospective/
FreeDOS and Floppy Bird
Show full content
FreeDOS and Floppy Bird

Going to start the year off with a rather unexpected banger. Many moons ago, during the great year of 2014, I released Floppy Bird, a boot-able clone of the infamous Flappy Bird written in 16-bit assembly.

floppybirdos

A while later, I added an M$-DOS compatible version in the form of a .COM executable weighting a whopping 8704 bytes, or 8.5 kilobytes. Could have probably saved a few more bytes, oh well!

Needless to say it has gotten a fair bit of attention in its early days, but never would have ever imagined that it would end up being bundled as a game with FreeDOS.

floppybird_freedos

I only found out about this last night and absolutely by accident, when YouTube ended up recommending me the official FreeDOS channel.

What a totallly unexpected surprise. Starting the year off on the right foot, amirite?

https://mihail.co/2023/01/01/freedos-floppy-bird/
2022 Retrospective
Show full content
2022 Retrospective

2022 has been a very interesting year to say the least, I never would have imagined that there will be not one, but two super duper big recessions in my lifetime. Yet here we are. Things are going to get a lot worse before they’ll get any better in the next couple of months.

Stay tuned for 2020: Season 4 coming soon to fine theaters and streaming services near you!

As always it’s not all gloom an doom, so let’s get things rolling with the mandatory last-year-in-commits chart graciously provided by none other than the greatest hub of them all, the mighty GitHub.

image-20221231143718656

In other news, this year I managed to keep at least one of my new year’s resolutions and ended up writing mini retrospectives at the end of each month.

This of course makes writing this end-of-the-year retrospective much easier and much shorter, since I do not have to spend several days digging up everything interesting that has transpired during the year.

Monthly Retrospective Archive

I am hoping to continue this tradition of monthly retrospectives next year.

Highlights

Be that as it may I’ll still call out some of the highlights of the year in below, just so that you don’t have to comb through the archives on New Year’s Eve no less.

Marmota

I added two additional features to my terminal emulator Marmota based on libvte, namely:

  • support for background images
  • support for runtime font scaling

marmota_font_scale

And yes, I still hate libvte. It’s kind of comforting to know that some things never change, right?

Flordle

I came a little bit late to the party with Flordle, right around the end of the Wordle craze, but I was quite happy with how it turned out.

flordle

It features a text-mode user interface and comes in two flavors:

  • boot-able from Floppy Disk, USB Stick or CD-ROM
  • M$-DOS compatible .COM executable

The oldest piece of real hardware I booted it up on was my ancient first generation [ASUS E-PC][asusepc].

Notes on Lua

Earlier in the month I published a 10000 words long blog post called Notes on Lua, where I dive into some of the peculiarities of using and embedding Lua into games and apps.

This started as something much smaller in scope, but then I kept extending it. However, there’s still a lot more that I could have covered but didn’t, so perhaps there will be a part-deux sometime next year.

Typora

I use Vim for pretty much everything, from writing code to documentation, however I made a leap of faith and bought a Typora license earlier in the year, and I’ve found myself using it almost exclusively for writing documentation and blog posts ever since.

There’s a 15-day trial version available if you want to give it a go.

typora

The End

Not going to have a list of New Year’s resolutions this year, but I do have some rather interesting things in the works for the year to come, so stay tuned.

As always, I’ll refrain from making any hard promises though, just in case.

https://mihail.co/2022/12/31/2022-retrospective/
Notes on Lua
Show full content
Notes on Lua

Avid readers of my blog and followers of mine probably can tell by now that most of my personal projects are written in C and Lua.

Would these be the the languages I’d take with me if I had to leave on a deserted island? The answer to that is a resounding yes.

Some orthodox C++, JS or even 16-bit assembly might rear their ugly head every now and then, but those instances can be considered the exceptions rather than rules.

What the heck is orthodox C++? If you asked that question, then you are in luck because I just happen to know the location of this perfect place, where you’ll find the answers you’ve been looking for your entire life.

With that said, I thought that I’d put together some notes on Lua in the hopes that it might help total newcomers, as well as people who might have dabbled with it to a certain extent in the past, but then decided not to go with it for one reason or another.

2022 is really the year of second chances, therefore perhaps these notes might change your mind and you’ll give Lua another try.

Lua is really an excellent companion language and one of the best (if not the best) languages out there for making your games and apps script-able.

It’s one of those languages where embeddability isn’t just an after thought or an unintended happy consequence, but it’s actually a core part of the language.

If I had to describe Lua in three words, I’d probably say something along the lines of compact, fast and flexible.

Without any further ado, let’s get started.

Non-zero index based arrays

When talking about Lua, one of the first, if not the absolute first thing that comes up is the non-zero index based arrays.

Languages where arrays start at 1 instead of 0 are evil and yada, yada! You probably heard this spiel a million times over.

While, this is a totally valid point to discuss, it’s really not that big of a deal as it might appear to the unsuspecting eye.

Why? Well, first of all nothing stops you from using 0 as an index in the first place so long you are aware of the implications of doing so.

local t = {}

for i=0,3 do
	t[i] = i
end

The code above is perfectly valid Lua. Now, what are the caveats or the implications?

  • can’t use #t because the length will not take into count the element at index 0
  • can’t use the ipairs(t) iterator, because once again it will skip the element at index 0

But, once again this is truly a non-issue for all intents and purposes. The mental shift that people say that is required is an imaginary one. Take my word for it.

When would you actually need zero based array indexing? I can think of two specific scenarios.

  • working with binary data and offsets
  • working with level/map data that you want to index into by turning x, y coordinates into an array index

In both of these cases you could really just do a +1 and everything would be just fine. On the other hand, you will most likely have a higher level interface that is more convenient to use rather than litter the entire code-base with inline index or offset computations.

If this was THE MAJOR THING that has been preventing you from trying out Lua, just ignore it and you might find yourself pleasantly surprised.

Procedural vs Functional vs OOP

Another thing that is sometimes brought up as kind of a negative when it comes to Lua is the fact that it doesn’t really forces you to pick and/or use any particular so called design patterns or styles.

Obviously, Lua is not the only language out there that does this, but I feel like Lua is still special in some ways when it comes to this because of its dynamic nature.

I also consider this fact to be one its strengths rather than shortcomings.

Procedural

Let’s take a look at a purely procedural way of doing things first.

function init_monster(t)
	t.health = 100
	t.speed = 50
	t.position = { x = 0, y = 0 }
end

function move_monster(t)
	local p = t.position
	p.x = p.x + t.speed
	p.y = p.y + t.speed
end

local t = {}
init_monster(t)
move_monster(t)

Another variation on this theme could something like this:

function create_monster()
	local t = {
		health = 100,
		speed = 50,
		position = { x = 0, y = 0 }
	}
	return t
end

local t = create_monster()
move_monster(t)

One could also do away with the so called constructor altogether and just declare the table inline.

local t = {
	health = 100,
	speed = 50,
	position = { x = 0, y = 0 }
}

move_monster(t)

Alternatively, one could also use a combination of inline declaration and an anonymous function.

local t = (function()
	return {
		health = 100,
		speed = 50,
		position = { x = 0, y = 0 }
	}
)()

move_monster(t)
Functional

What about all that totally amaze-balls functional hotness? No problem at all, Lua got you covered.

function map(t, f)
	local result = {}

	for i, v in ipairs(t) do
		result[i] = f(v, i)
	end

	return result
end

function create_monster(id)
	local t = {
		id = id,
		health = 100,
		speed = 50,
		position = { x = 0, y = 0 }
	}
	return t
end

function move_monster(t)
	local p = t.position

	p.x = p.x + t.speed
	p.y = p.y + t.speed

	return t
end

function add_log(message, f)
	return function(v)
		print(message, v.id)
		return f(v)
	end
end

map(
	map(
		{ 1, 2, 3 },
		create_monster
	),
	add_log(
		"moving monster",
		move_monster
	)
)
OOP

And now, what everybody has been waiting for. Drum-rolls! Object Oriented Programming, or Brain Damage Oriented Programming as I like to call it these days.

Once your mind has been infested with the OOP paradigm it’s always impossible to rid of it, at least not without significant efforts.

local CLASS_METATABLE <const> = {
	__call = function(self, ...)
		local instance = setmetatable({}, self)

		if type(instance.init) == "function" then
			instance:init(...)
		end

		return instance
	end
}

function class(klass, super_klass)
	local klass = klass or {}

	for k, v in pairs(super_klass or {}) do
		klass[k] = v
	end

	klass.__index = klass
	return setmetatable(klass, CLASS_METATABLE)
end

Monster = class(Monster)
Monster.init = function(self, name, kind, speed)
	self.name = name
	self.kind = kind
	self.speed = speed or 5
	self.position = { x = 0, y = 0 }

	return self
end
Monster.move = function(self, x, y)
	local p = self.position
	local speed = self.speed

	p.x = p.x + speed
	p.y = p.y + speed

	return self
end

Zombie = class(Zombie, Monster)
Zombie.init = function(self, name)
	return Monster.init(self, name, "zombie")
end
Zombie.print = function(self)
	print(self.kind, self.name)
	return self
end

local zombie = Zombie("peter")
zombie:move():print()
DSL

Now, what if you want a DSL to describe your monster types in a nice data-oriented fashion? Lua really shines when it comes to this, considering that it has its roots as a data description language of sorts.

local MONSTER_TYPE_METATABLE <const> = {
	__call = function(self, t)
		assert(t.name ~= nil, "monster type needs a valid name")
		assert(self[t.name] == nil, "duplicate monster type")

		self[t.name] = t
		table.insert(self, t)

		return t
	end
}

monster = setmetatable({}, MONSTER_TYPE_METATABLE)

monster {
	name = "zombie",
	speed = 5
}

monster {
	name = "ogre",
	speed = 15
}

for _, v in ipairs(monster) do
	print(v.name, v.speed)
end

print("ogre speed", monster.ogre.speed)

One nice feature that makes this possible is that the paranthesis around functions can be omitted if said function has a single string or table argument.

This feature makes Lua perfect for creating DSLs of all sorts.

Metatables and Metamethods

Both OOP and DSL examples above made use of so called metatables as well as metamethods in the form of __call in order to achieve the desired and necessary syntactic sugar.

A metatable is table that contains one or more metamethods and then is set as the metatable of another table.

Any given table at any given time can have a single metatable associated with it, which can be set and retrieved by setmetable and getmetable respectively.

A prime example that I always like to give to illustrate the concept is defining a metatable that implements the __tostring metamethod in order to provide pretty-printing capabilities.

local sprintf = string.format

local METATABLE <const> = {
	__tostring = function(self)
		return sprintf("{ x = %d, y = %d }", self.x, self.y)
	end
}

t = setmetatable({ x = 11, y = 22 }, METATABLE)
print(t)

With this setup, print(t) will print out { x = 11, y = 22 } instead of table: 0xdeadbeef.

Even if you do not intend to do any heavy meta-programming like in the examples given above, you should still take the time and familiarize yourself with the concept of metatables and metamethods.

You can learn more about metatables and metamethods by clicking here.

Coroutines

Lua comes with support for coroutines out of the box. I won’t go into a lot of details and will provide only a relatively simple and easy to understand example below.

function monster_wait(self, amount)
	local t = 0
	while t < amount do
		coroutine.yield()
		t = t + sys.delta_time
	end
end

function monster_think(self, dt)
	while true do
		local player = monster_find_player(self)

		if player then
			print('shooting at player')
			monster_shoot_at_player(self, player)
		end

		monster_wait(self, 0.5)
	end
end

local t = create_monster()
local co = coroutine.create(monster_think)
coroutine.resume(co, t, dt)
coroutine.resume(co, t, dt)
coroutine.resume(co, t, dt)

One interesting thing to note here is that one can yield anywhere from within the coroutine, even from another function as illustrated by the monster_wait function.

You can learn more about coroutines by clicking here.

Modules and Packages

Lua doesn’t have any special keywords or language features per-say when it comes to providing support for modules and packages alike.

If you think about it, both modules and packages are just Lua tables containing functions.

local math = require "math"
print(math.pi)

Sometimes the words library, module and package are used interchangeably when referring to modules.

So, how is this implemented behind the scenes?

The require function, which is just a good old regular function, ends up looking for the required module or package by parsing two variables. Namely package.path and package.cpath.

> package.path
./?.lua;./?/init.lua
> package.cpath
./?.so

What is the difference between path and cpath? I am pretty sure that you guessed already, cpath is used to look for Lua native extensions in the form of dynamic linking libraries or shared objects, while path is exclusively for looking up Lua source files, including pre-compiled ones with luac given that they still use .lua as an extension.

There’s no real difference as far as the user is concerned between the two. You just call require and whatever matches first will be returned, or if the given module cannot be found in either paths an error that indicates this fact will be raised.

It should go without saying that both of these variables can be modified at run-time and the changes will take effect the next time require is called with module name that hasn’t been loaded or required previously.

Another interesting bit of trivia that I’d like to mention is the fact that by looking at the directory separator one can determine the platform the Lua interpreter. On Windows this is going to be \ and on Unix-likes it’s going to be / obviously.

This trick is sometimes used in Lua scripts designed to be executed via the command line Lua interpreter, also known as lua or lua.exe respectively.

If you have a Lua script called monster.lua then you just require it like this.

-- without parenthesis
local monster = require "monster"

-- with parenthesis
local zombie = require("zombie")

Of course this assumes that your monster.lua looks something like this.

local M = {}

M.create = function(name)
	return { name = name }
end

return M

What about packages? If you have a directory called monster with a Lua script called init.lua in it, then once again you just require the same way.

local monster = require "monster"

This in in turn will load the init.lua from within that directory.

Remember that monster.lua takes priority over monster/init.lua as per the patterns defined in package.path, so if you happen to have both of them within the very same directory, it will end up requiring monster.lua instead.

What if you want to be able to require from other sources like a ZIP archived backed virtual file system?

You can create your own custom loader and insert into package.loaders before or after the default loader as you see fit as illustrated in the example below.

function create_zip_archive_loader(archive_filename)
    local archive = assert(zip.open(archive_filename, "r"))

    return function(name)
        local filename = string.format(
            "%s.lua",
            string.gsub(name, "%.", "/")
        )

        local contents = archive:read(filename)
        if contents then
            return assert(loadstring(contents, filename))
        end

        return string.format(
            "could not find '%s' in zip archive '%s'",
            filename,
            archive_filename
        )
    end
end

table.insert(package.loaders, 2, create_zip_archive_loader("scripts.zip"))

Not too shabby right?

In most cases you will really want to just roll your own import, especially when it comes to games where you will also want sandboxing and you are almost always going to be loading from a virtual file system of some description.

With all that being said, I’d recommend creating your own import function and exposing that instead of the built-in require.

function create_import(archive_filename)
    local archive = assert(zip.open(archive_filename, "r"))

    return function(name)
        local filename = string.format(
            "%s.lua",
            string.gsub(name, "%.", "/")
        )
        local contents = assert(archive:read(filename))
        return assert(loadstring(contents, filename))
   	end
end

import = create_import("scripts.zip")

-- use it just like you would the built-in `require`
local monster = import "monster"
Sandboxing

Sandboxing is another thing that Lua can do without the need for any complicated or fancy language features.

If you worked with Lua before you probably stumbled upon the mysterious _G table, which is really an alias to the table that serves as the global environment when the lua_State is first createda nd initialized.

Any global variables or functions that one defines will end up in this table. The alias is provided for convenience purposes, more exactly cases like these:

  • enumerating all globally defined variables or functions
  • interacting in some unusual way with the global environment from within a nested scope

As a result, sandboxing in its most basic and pure form is simply a matter of shadowing the global environment table with another table called _ENV defined in the local scope.

function sandbox()
	local _ENV = {
        print = print
    }

    return function(message)
    	print(message)
        print(_VERSION)
    end
end

sandbox()("hello world")

If you run this, you’ll see that it will print hello world and nil, because the global constant _VERSION wasn’t included in the _ENV table thus it is not accessible from within the returned function. Which is really a closure, but more on that later.

In most real world scenarios however, you’ll probably never set _ENV by hand and let loadstring or loadfile do it internally while loading the desired chunk and setting its upvalue to the desired environment.

And now, I am going to illustrate sandboxing via loadfile by combining the DSL and import examples shown above in order to give you a more realistic approach on how one would go about it.

local MONSTER_TYPE_METATABLE <const> = {
	__call = function(self, t)
		assert(t.name ~= nil, "monster type needs a valid name")
		assert(self[t.name] == nil, "duplicate monster type")

		self[t.name] = t
		table.insert(self, t)

		return t
    end
}

function create_monster_importer()
	local env = {
		monster = setmetatable({}, MONSTER_TYPE_METATABLE),
		print = print
	}

	env.import = function(name)
		local filename = string.format(
			"%s.lua",
			string.gsub(name, "%.", "/")
		)
		local chunk = assert(loadfile(filename, "tb", env))
		return chunk()
	end

	return env
end

local monster_importer = create_monster_importer()

monster_importer:import("monsters/ogre")
monster_importer:import("monsters/zombie")

for i, v in ipairs(monster_importer.monster) do
	print(i, v.name, v.speed)
end

Then monsters/ogre.lua could look like this.

monster {
    name = "ogre",
    speed = 15
}

monster {
    name = "zombie_boss",
    speed = 20
}

And finally monsters/zombie.lua which is more of the same but in a different Lua script.

monster {
    name = "zombie",
    speed = 5
}

monster {
    name = "zombie_boss",
    speed = 10
}

Please note how we pass in the env table as the last argument of loadfile. The loadstring function supports the same arguments as well and can be used in the same manner.

This env table ends up overriding the global environment by altering the upvalue of the loaded chunk and only the functions and/or variables defined within said table will be available within said chunk when executed.

In other words, trying to call any other functions than monster, print and import will result in an error, simply because nothing else has been defined in the env table.

Also, any variables, functions that might be defined within the loaded chunks, they also end up in the env table rather than the global environment table.

Obviously, if we are talking about user generated content like in the case of Roblox, then obviously additional measures have to taken, like preventing infinite loops and so forth, but in most other locally moddable games and/or apps, this is more than sufficient to provide a safe, intuitive and a super convenient data-driven way to define configuration, entities, levels, maps and so on.

C <-> Lua interaction

I feel like we traveled down far enough through the proverbial rabbit hole at this point to bring the bi-directional interaction between C and Lua to the table in the context of embedding the Lua interpreter inside a game or an application.

Considering that this is quite a big topic to tackle, I’ll try to be as concise as possible without sacrificing too much in terms of details that are important to gain a better understanding of how things are glued together.

What I noticed is that most people end up under or over exposing and never really taking the time to find just the right balance that actually make sense and feels what I like to call Lua-like for whatever its worth.

The over exposing is generally done by people who happen be using C++. Which of course is to be expected, considering that any prolonged exposure to C++ or OOP in general is well known to cause permanent and irreversible brain damage.

It’s never ever desirable to expose C++ classes 1:1 in a purely OOP fashion. I am not your mum, and I can’t tell you what to do with your adult life, but PRETTY PLEASE, DO NOT EVER DO THIS, if for nothing else but respect for you fellow human beings you happen to be sharing this piece of rock floating in space with.

And even more importantly, do not use one of those “auto-class-binding generator” like libraries. If your mind just took you to a very dark place called BOOST, please find the nearest church and pour some holy water on over your head, but it might be too late, so please don’t hold your breath.

Writing your own bindings by hand is the way to go, even if it might look intimidating at first glance.

Calling Lua functions from C

However, before go into all that let’s take a look at how one would go about calling Lua functions from C.

In the case of a game, if you have done things right, you do not ever really need more than 3 global functions to serve as trusty entry points into the world of Lua.

By taking this route, you make it very easy to profile how much time is spent in Lua every frame, which is a very important and rather convenient side effect to consider.

local escape_key = nil

function on_init(argv)
    -- call once on init
    escape_key = input_key_index("escape")
end

function on_frame(dt, w, h)
    -- called once every frame
    if input_key_pressed(escape_key) then
		print("key pressed")
    end
end

function on_quit()
	-- called once on quit
end

On the C side of things, you create your lua_State and then lookup and call the 3 functions.

Proper error handling has been largely omitted for purposes of brevity.

lua_State *L = luaL_newstate();
if(L == NULL)
{
    // fatal error failed to create Lua state
    return -1;
}
// load and initialize the Lua standard library
luaL_openlibs(L);

if(!script_on_init(L, argc, argv))
{
    // fatal error so just return early
    return -1;
}

while(running)
{
    double dt = /* calculate delta time in ms/sec */;

    process_os_events();

    script_on_frame(L, dt, w, h);

    // swap GL buffers
    // vsync and/or apply frame limiting
}

script_on_quit(L);

lua_close(L);
L = NULL;

Now, you might be tempted to ask, should one even call process_os_events() on in C or simply expose it to Lua and then let the user decide if they care about processing events during any particular frame, or perhaps also pick and choose which events they might want to process.

This would also mean that one doesn’t have to change the change and recompile the C side of things in situations where certain events are irrelevant for the current game. For example, if the game doesn’t support joysticks, then there’s no reason to process anything joystick related.

function on_frame(dt, w, h)
	for event in poll_events() do
		if event.type == EVENT_TYPE_KEYDOWN && event.key == 27 then
    		print("key pressed")
		end
	end
end

Both approaches have their pros and cons. I’ll let you make up your own mind about which one resonates more with your particular style and needs.

Alright, but how do you actually call any of these Lua functions from C (the host game/application)?

bool script_on_init(lua_State *L, const int argc, const char *argv[])
{
    int i, ret;

 	if(lua_getglobal(L, "on_init") != LUA_TFUNCTION)
    {
        lua_pop(L, 1);
        return false;
    }

   	lua_createtable(L, argc, 0);
    for(i = 0; i < argc; i++)
    {
        lua_pushstring(L, argv[i]);
        lua_rawseti(L, -2, i);
    }

    ret = lua_pcall(L, 1 /* one argument */, 0, 0);
    if(ret != 0)
    {
        fprintf(stderr, "Lua Error:\n%s\n", lua_tostring(L, -1));
        lua_pop(L, 1);
        return false;
    }

    return true;
}

Alright, I admit it that there’s a lot going on and I’ll try to point everything out almost in a line by line fashion, so bear with me. It will be worth it in the end.

The first order of business is to lookup the on_init function by name in the global environment, which should sound familiar since I mentioned it briefly when I talked about sandboxing above.

if(lua_getglobal(L, "on_init") != LUA_TFUNCTION)
{
	lua_pop(L, 1);
    return false;
}

This is done by using the lua_getglobal function, which conveniently pushes the value of the key "on_init" into the stack and then returns the type of said value.

Before going much further, it’s worth noting that the Lua C API is stack based, which means that one interacts with the lua_State by pushing and popping values into and from aforementioned stack.

Values pushed into the stack can be referred to by a numeric index. This index can be positive or negative.

A positive index can be used to refer to values from the bottom of the stack going in an upwards direction, while a negative index can be used to refer to values from the top of the stack going in a downwards direction.

So let’s rewrite the above piece of code in a slightly different way to illusrate the use of a negative index when referring to a value at the top of stack, which translates to index -1.

lua_getglobal(L, "on_init");
if(!lua_isfunction(L, -1))
{
	lua_pop(L, 1);
    return false;
}

Here we ignore the return value of lua_getglobal and use the lua_isfunction to find out if the value pushed into the top of the stack by lua_getglobal is of type LUA_TFUNCTION.

The usage of lua_pop should be self explanatory and it is simply used to pop values off the top of he stack.

One important distinction to note here is that the second argument is the actual count of values to pop and it doesn’t represent a stack index.

Now there’s a third way to rewrite this lookup that we just did which also illustrates popping two values of the top of the stack in one fell swoop.

lua_pushglobaltable(L);
if(lua_getfield(L, -1, "on_init") != LUA_TFUNCTION)
{
    lua_pop(L, 2);
    return false;
}
lua_pop(L, 1);

Of course we could also just ignore th return value of lua_getfield and then use lua_isfunction in order to determine the type of said value just like we did in the previous implementation.

Speaking of lua_isfunction, I want to point out that the fact that it’s just a good old fashioned convenience macro, which expands to an expression making use of the lua_type function.

#define lua_isfunction(L, n) (lua_type(L, (n)) == LUA_TFUNCTION)

Alright, let’s take a look at a forth variation of the lookup.

lua_pushglobaltable(L);
lua_pushstring(L, "on_init");
if(lua_gettable(L, -2) != LUA_TFUNCTION)
{
    lua_pop(L, 2);
	return false;
}
lua_pop(L, 1);

Wait a minute, why are we still popping 2 and then 1 off the stack? Shouldn’t we also account for the value pushed by lua_pushstring?

No, because lua_gettable pops it off the stack and then pushes the resulting value of the lookup in its place.

Instead of lua_pushstring could have also used lua_pushliteral which is another macro that ensures that its argument is a compile-time constant string literal and not a run-time variable.

#define lua_pushliteral(L, s) lua_pushstring(L, "" s)

This is done by the means of a preprocessor trick that makes the compiler throw an error when s is not a compile-time constant string literal.

How does it do that? Well, we know that the preprocessor is kind enough and happens concatenate adjacent compile-time constant string literals, but will refuse to concatenate a variable and a string literal due to the expression not being computable at compile-time.

And now it’s time for the fifth and final version of looking up our on_init function. It’s true, cross my heart and hope to die or pinkie promise, whichever you happen to fancy.

If we look at lua_pushglobaltable we notice that it also happens to be a macro.

#define lua_pushglobaltable(L) \
    ((void) lua_rawgeti(L, LUA_REGISTRYINDEX, LUA_RIDX_GLOBALS))

Casting to (void) is just another preprocessor trick to silence any unwanted compiler warnings regarding unused return values.

You might have seen this used for doing the same when it comes to unused variables.

#define UNUSED(x) (void) (x)

int main(int argc, char *argv[])
{
    UNUSED(argc);
    UNUSED(argv);

    return 0;
}

Syntatic macro sugar and spice is what makes your code look pretty and nice. I am a poetic genius, aren’t I? I am sure you agree, so let’s carry on. Time is of the essence.

lua_rawgeti(L, LUA_REGISTRYINDEX, LUA_RIDX_GLOBALS));
lua_pushliteral(L, "on_init");
if(lua_rawget(L, -2) != LUA_TFUNCTION)
{
    lua_pop(L, 2);
    return false;
}
lua_pop(L, 1);

Please ignore the LUA_REGISTRYINDEX pseudo-index for now, we’ll come back to it in a second and all will be revealed before your eyes.

The lua_rawgeti and lua_rawget operate raw on a given table hence their name.

What this means is that they do not call any of the table’s metamethods like __index when doing lookups. Of course if the table in question doesn’t have a metatable or doesn’t implement the __index metamethod then it doesn’t really matter, but I am sure you get the idea.

The lua_gettable and lua_getfield functions on the other hand will end up calling the metamethods in question.

When to use one versus the other? Generally speaking you’d use the raw access methods when implementing metamethods themselves to avoid infinite loops, or when you simply don’t care about metamethods at all.

It should go without saying that raw access has less overhead especially if the defined metamethods are heavy duty and do otherwise complicated lookups.

What if the function that we wanted to lookup isn’t in the global environment table, but rather in a module or package.

game = game or {}
game.on_init = function(argv)
	-- noop
end

Given the five examples above, I think we can kind of tell how to go about that.

if(lua_getglobal(L, "game") != LUA_TTABLE)
{
    lua_pop(L, 1);
    return false;
}
if(lua_getfield(L, -1, "on_init") != LUA_TFUNCTION)
{
    lua_pop(L, 2);
    return false;
}
lua_pop(L, 1);

Easy peasy, lemon squeezy.

Now that we mastered the dark arts of looking up the function we want to call, written our thesis and gotten our well deserved PhD, it’s time to move on to the next phase, which involves preparing the argument that we want to call the function with.

lua_createtable(L, argc, 0);
for(i = 0; i < argc; i++)
{
	lua_pushstring(L, argv[i]);
    lua_rawseti(L, -2, i);
}

First we create a new table and push it into the stack by calling lua_createtable.

The second and third arguments allow us to reserve memory for numeric and key indices. These come handy when we happen to know the expected or approximate amount of elements that we wish to insert into said table.

This of course can also help us reduce the number of extraneous memory allocations as the table grows during the insertion process of the elements.

In our particular case we know the exact amount we want and that is equal to argc which is the number of command line arguments that were passed to our executable.

Then we simply iterate over the arguments and set each argument at a given numeric index by the meas of lua_rawseti which just like its companion getter lua_rawgeti will not try to invoke any of the metamethods as we established before.

The second argument to lua_rawseti is set to -2 because the table we just created is on position two downwards from the top of the stack.

After it sets the value, lua_rawseti will also pop the string we previously pushed into the stack.

You might have noticed that we start at index 0 rather than 1, which is not a typo by the way. Why?

function on_init(argv)
	for _, arg in ipairs(argv) do
       -- noop
    end
    print(argv[0])
end

This allows us access to the name of the executable if we need it, without having to worry about skipping the first argument when working with the table in every other case.

function on_init(argv)
	for i=2, #argv do
		local arg = argv[i]
       	-- noop
    end
    print(argv[1])
end

Now that our table (array) of command line arguments is ready, we can go ahead and finally call our on_init function.

ret = lua_pcall(L, 1 /* one argument */, 0, 0);
if(ret != 0)
{
	fprintf(stderr, "Lua Error:\n%s\n", lua_tostring(L, -1));
    lua_pop(L, 1);
    return false;
}

When lua_pcall is called, it will pop both the function and the number of arguments indicated by the first argument off the stack.

If there have been any errors with calling the function it will return a non zero value and push the error message into the top of the stack, which we can retrieve and display the user in some form or another.

The p in lua_pcall comes from protected, meaning that it is guaranteed to return safely in case of any errors and unwind the stack as expected.

What if we wanted to allow on_init to return an error code and then propagate that up to main() and use it as an exit code?

function on_init(argv)
	-- noop
    return -1
end
int script_on_init(lua_State *L, const int argc, const char *argv[])
{
    int i, ret;

 	if(lua_getglobal(L, "on_init") != LUA_TFUNCTION)
    {
        lua_pop(L, 1);
        return false;
    }

   	lua_createtable(L, argc, 0);
    for(i = 0; i < argc; i++)
    {
        lua_pushstring(L, argv[i]);
        lua_rawseti(L, -2, i);
    }

    ret = lua_pcall(L, 1 /* one argument */, 1 /* one return value */, 0);
    if(ret != 0)
    {
        fprintf(stderr, "Lua Error:\n%s\n", lua_tostring(L, -1));
        lua_pop(L, 1);
        return false;
    }

    if(lua_isnumber(L, -1))
    {
        ret = lua_tonumber(L, -1);
    }

    lua_pop(L, 1);
    return ret;
}

int main(int argc, char *argv[])
{
    int ret;
    lua_State *L;

    ret = script_on_init(L, argc, (const char **) argv);
    if(ret != 0)
    	return ret;

    // ...

    return ret;
}

The first thing that you might notice is that lua_pcall now has its third argument set to 1 which is the number of return values we expect on_init to have.

ret = lua_pcall(L, 1 /* one argument */, 1 /* one return value */, 0);

The second thing to call out is the fact that we check if said return value is a number. If it is, then we grab the actual value and pop it off the stack.

if(lua_isnumber(L, -1))
{
	ret = lua_tonumber(L, -1);
}
lua_pop(L, 1);

This enables us to also not have to explicitly return anything in on_init and in such cases we will just assume that the return value is equal to 0.

function on_init(argv)
	-- noop
end

Obviously, we could also just print an error and return a specific error code in case it wasn’t a number if we so desired. But, personally I prefer to make things optional when it makes sense to do so.

Now let’s take a look at how would one go about calling on_quit.

void script_on_quit(lua_State *L)
{
    int ret;

    if(lua_getglobal(L, "on_quit") != LUA_TFUNCTION)
    {
        lua_pop(L, 1);
		return;
    }

    ret = lua_pcall(L, 0, 0, 0);
    if(ret != 0)
    {
        fprintf(stderr, "Lua Error:\n%s\n", lua_tostring(L, -1));
        lua_pop(L, 1);
    }
}

I don’t think that on_quit needs any discussion or clarifications, it should all self explanatory.

And, finally the implementation of calling on_frame.

void script_on_frame(
	lua_State *L,
    const double dt,
    const int w,
    const int h
)
{
    int ret;

 	if(lua_getglobal(L, "on_frame") != LUA_TFUNCTION)
    {
        lua_pop(L, 1);
        return;
    }

    lua_pushnumber(L, dt);
    lua_pushinteger(L, w);
    lua_pushinteger(L, h);

    ret = lua_pcall(L, 3 /* arguments argument */, 0, 0);
    if(ret != 0)
    {
        fprintf(stderr, "Lua Error:\n%s\n", lua_tostring(L, -1));
        lua_pop(L, 1);
        return false;
    }
}

The only thing we haven’t see before is the fact that on_frame expects and receives 3 arguments, which are pushed in order into the stack and then lua_pcall is passed in 3 as its second argument.

Considering this function is going to be called every frame, which can mean 60 or more times per second, depending on the frame rate, it would be nice to avoid having to do the lookup by name every single time.

A way to do this is by grabbing a reference which is then stored in the global registry that I mentioned before and told you that we’ll come back to later.

if(lua_getglobal(L, "on_frame") != LUA_TFUNCTION)
{
	lua_pop(L, 1);
    return;
}

int on_frame_ref = luaL_ref(lua, LUA_REGISTRYINDEX);

The idea behind this is to grab the reference once on startup and then just reuse said reference avoiding the more expensive global lookup on every frame.

It is also worth pointing out that grabbing a reference will prevent Lua from garbage collecting the object or function in question that the reference is pointing to.

The sister function luaL_unref can be used to release a reference once we are done with it.

So let’s take a look at how this affects the implementation of script_on_frame.

void script_on_frame(
    lua_State *L,
    const int ref,
    const double dt,
    const int w,
    const int h)
{
    int ret;

 	lua_rawgeti(L, LUA_REGISTRYINDEX, ref);

    lua_pushnumber(L, dt);
    lua_pushinteger(L, w);
    lua_pushinteger(L, h);

    ret = lua_pcall(L, 3 /* arguments argument */, 0, 0);
    if(ret != 0)
    {
        fprintf(stderr, "Lua Error:\n%s\n", lua_tostring(L, -1));
        lua_pop(L, 1);
        return false;
    }
}
Calling C functions from Lua

Now that we know how to call Lua functions from C, it is time to dive into exposing some C functions that then can be called from Lua.

int create_window(lua_State *L)
{
    return 0;
}

Notice the signature of the function, it has a single argument which is the lua_State and returns an int.

The return value indicates how many values it should pop from the stack and return to the caller. In the example above, this is 0, meaning that the function is not expected to push anything into the stack and as a direct result not expected to return anything.

Let’s take a look at a more full fledged example.

int create_window(lua_State *L)
{
    const char *title;
    int w, h;

    title = luaL_checkstring(L, 1);
    w = luaL_checkinteger(L, 2);
    h = luaL_checkinteger(L, 3);

   	if(!os_window_create(title, w, h))
    {
        lua_pushboolean(L, false);
        lua_pushliteral(L, "window creation failed");
        return 2;
    }

    lua_pushboolean(L, true);
    lua_pushnil(L);
    return 2;
}
local ok, err = create_window("My Window", 640, 480)
if not ok then
   print(err)
end

local ok = assert(create_window("My Window", 800, 600))

The create_window function takes 3 arguments, a string and two integers, which we use retrieve the values of by calling the family of luaL_checktype functions.

These simply check the type of the value in the stack at the given index, if it matches the expected type, then the value is returned, otherwise a type-check like error is raised.

title = luaL_checkstring(L, 1);
w = luaL_checkinteger(L, 2);
h = luaL_checkinteger(L, 3);

We could have also used negative stack indexing if we so desired.

title = luaL_checkstring(L, -3);
w = luaL_checkinteger(L, -2);
h = luaL_checkinteger(L, -1);

These two are obviously equivalent. The question now arises when to use one versus the other?

My recommendation would be to use negative stack indexing when looking up arguments only in the case of auxiliary and/or utility type functions which cannot make any assumptions about the stack other than what is currently at the top.

Take a look at the example below.

int set_window_title(lua_State *L)
{
    const char *title;

    title = luaL_checkstring(L, -1);

    os_set_window_title(title);

    return 0;
}

lua_pushliteral(L, "Nice Window");
set_window_title(L);
lua_pop(L, 1);

The second thing to note about the implementation of create_window is how it returns a tuple (fancy word for two values) rather than one as indicated by the return value of 2.

This so called pattern is used by the built-in Lua standard library and its kind of an unwritten rule when it comes to handling certain types of errors without raising an fatal error explicitly by calling the luaL_error, luaL_typerror, and luaL_argerror family of functions.

In order to catch errors raised by these functions one must make use the two protected flavors of lua_call, namely lua_pcall and lua_xpcall respectively.

Once more the quintessential question arises in the form of when to use one versus the other?

My recommendation is to use the tuple approach in cases when the error is about dealing with an external resource of some sort (i.e file system error) that can be retried and might not be considered a fatal error, while raising actual errors via the means of luaL_error should be reserved for actual fatal errors that might require stopping execution of the script or even the host application altogether.

local f, err = io.open("test.lua", "rb")
if not f then
	print(err)
end

local f = assert(io.open("test.lua", "rb"))

The use of strings rather than numerical constants when passing in options (flags) to a certain function is yet another common pattern used by the built-in Lua standard library, as well as third party libraries.

While this makes total sense and seems like a reasonable thing to do, and in many cases is probably what one would end up doing anyway, in the context of a game, if a particular function is called every frame, it’s probably not desirable have a whole lot of strcmp happening in there.

With that in mind, I’d recommend to use good old numerical constants that can be OR-ed together as flags; which is what one would do on the C side of things anyway in this particular situation.

#define BIT(x) (1 << (x))
#define _STRINGIFY(x) #s
#define STRINGIFY(x) _STRINGIFY(x)

enum
{
    WINDOW_FULLSCREEN = BIT(0),
    WINDOW_RESIZABLE  = BIT(1),
    WINDOW_CENTERED   = BIT(2)
    // ...
};

#define script_set_constant(L, c) \
	do \
	{ \
		lua_pushinteger(L, c); \
		lua_setglobal(L, STRINGIFY(c)); \
	} while(0)

// ...

script_set_constant(L, WINDOW_FULLSCREEN);
script_set_constant(L, WINDOW_RESIZABLE);
script_set_constant(L, WINDOW_CENTERED);
// ...

The BIT helper macro should be self explanatory as it shifts 1 with N given as an argument giving us a series of power of twos.

In the case of STRINGIFY we apply an extra level of indirection by wrapping it in another macro to ensure that it always evaluates to what we expect it to.

This indirection is often used also when defining a macro for concatenation.

#define _CONCAT(a, b) a ## b
#define CONCAT(a, b) _CONCAT(a, b)

Finally, the do { ... } while(0) within the script_set_constant macro is a standard trick for achieving multi-line macros that work with single-line statements without an explicit { ... } block.

if(dev_mode)
    script_set_constant(L, DEV_MODE);

But, if you have written any non-trivial amounts of C in your natural life time, these so called tricks should already be in your daily arsenal and not unfamiliar in any shape or form.

On the Lua side our create_window function now can be called by OR-ing together the flags.

create_window("My Window", 640, 480, WINDOW_RESIZABLE | WINDOW_CENTERED)

In the sample implementation above, create_window simply returns true or false depending if the window creation has succeeded or not. This is fine if you only ever have one window, but what if you want to have more than one?

Well, in that case you probably want to return a handle or a pointer which then can be passed in as an argument to a function like close_window.

This is where so called userdata enters the picture. Lua happens to offer two flavors of userdata.

Feature(s) Light userdata Full userdata GC (garbage collection) no yes Metatables no yes __gc metamethod no yes __eq metamethod no yes == (equality) same “pointer” same “object” or via __eq

Let’s re-implement create_window by using light userdata.

int create_window(lua_State *L)
{
    const char *title;
    int w, h;
    uint32_t flags;
    os_window_t *window;

    title = luaL_checkstring(L, 1);
    w = luaL_checkinteger(L, 2);
    h = luaL_checkinteger(L, 3);
    flags = luaL_checkinteger(L, 4);

    window = os_window_create(title, w, h, flags);
    if(window == NULL)
    {
        lua_pushnil(L);
        lua_pushliteral(L, "window creation failed");
        return 2;
    }

    lua_pushlightuserdata(L, window);
    lua_pushnil(L);
    return 2;
}

Let’s implement the close_window function now in order to showcase how one would use said light userdata in another function.

int close_window(lua_State *L)
{
    os_window_t *window;

    luaL_checktype(L, 1, LUA_TLIGHTUSERDATA);

    window = lua_touserdata(L, 1);

    if(window != NULL)
        os_window_close(window);

    return 0;
}

It should be self evident that light userdata is just a good old C pointer. Which means that we can also store any sort of value as light userdata so long the size of value in question happens to be less than or equal to the size of sizeof(void *) or uintptr_t.

int create_window(lua_State *L)
{
    const char *title;
    int w, h;
    uint32_t flags;
    os_window_handle_t window;

    title = luaL_checkstring(L, 1);
    w = luaL_checkinteger(L, 2);
    h = luaL_checkinteger(L, 3);
    flags = luaL_checkinteger(L, 4);

    window = os_window_create(title, w, h, flags);
    if(window == OS_WINDOW_INVALID)
    {
        lua_pushnil(L);
        lua_pushliteral(L, "window creation failed");
        return 2;
    }

    lua_pushlightuserdata(L, (void *) (uintptr_t) window);
    lua_pushnil(L);
    return 2;
}

int close_window(lua_State *L)
{
    os_window_handle_t window;

    luaL_checktype(L, 1, LUA_TLIGHTUSERDATA);

    window = (os_window_handle_t) (uintptr_t) lua_touserdata(L, 1);

    if(window != OS_WINDOW_INVALID)
        os_window_close(window);

    return 0;
}

Now it is time to re-implement the create_window and close_window functions by using full userdata.

typedef struct
{
    os_window_t *window;
    uint32_t flags;
} window_userdata_t;

#define WINDOW_METATABLE "WINDOW *"

int close_window(lua_State *L);

static const luaL_Reg window_metamethods[] = {
	{ "__gc", close_window },
	{ "__close", close_window },
	{ NULL, NULL }
};

int create_window(lua_State *L)
{
    const char *title;
    int w, h;
    uint32_t flags;
    os_window_t *window;
    window_userdata_t *window_userdata;

    title = luaL_checkstring(L, 1);
    w = luaL_checkinteger(L, 2);
    h = luaL_checkinteger(L, 3);
    flags = luaL_checkinteger(L, 4);

    window = os_window_create(title, w, h, flags);
    if(window == OS_WINDOW_INVALID)
    {
        lua_pushnil(L);
        lua_pushliteral(L, "window creation failed");
        return 2;
    }

    window_userdata = lua_newuserdata(L, sizeof(window_userdata_t));
    if(window_userdata == NULL)
        return luaL_error(L, "out of memory");

    window_userdata->window = window;
    window_userdata->flags = flags;

    if(luaL_newmetatable(L, WINDOW_METATABLE))
        luaL_setfuncs(L, window_metamethods);

    lua_setmetatable(L, -2);

    lua_pushnil(L);
    return 2;
}

int close_window(lua_State *L)
{
    window_userdata_t *userdata;

   	userdata = luaL_checkudata(L, 1, WINDOW_METATABLE);
    if(userdata == NULL)
        return luaL_typeerror(L, 1, WINDOW_METATABLE);

    if(userdata->window != NULL)
    {
        os_window_close(userdata->window);
        userdata->window = NULL;
    }

    return 0;
}

The first thing that should stand out here is the use of lua_newuserdata which is used to allocate memory in order to hold our window_userdata_t struct.

The second thing that might stand out is this lovely piece of code.

if(luaL_newmetatable(L, WINDOW_METATABLE))
	luaL_setfuncs(L, window_metamethods);

lua_setmetatable(L, -2);

The luaL_newmetatable will attempt to lookup if there is a metatable with the provided name, if there is one it will push it into the top of the stack and return 0, otherwise it will create a brand new one and return 1.

In the case it ended up creating a new one, which would be the very first time create_window was called, we want to initialize the metadata and set the relevant metamethods we care about, namely __gc and __close. We do this by callingluaL_setfuncs with the array of function definitions.

We’ll come back and go into more detail when it comes to luaL_setfuncs later.

Finally, lua_setmetatable will set this metatable as the metatable of the newly created full userdata.

We could of course move this out of the create_window function and do it once when we setup the initial lua_State, in which case setting the metatable inside create_window could be simplified down to calling luaL_setmetatable instead of lua_setmetatable. Notice the uppercase L.

luaL_setmetatable(L, WINDOW_METATABLE);

Another thing I’d like call out is the way the userdata is being retrieved in close_window.

userdata = luaL_checkudata(L, 1, WINDOW_METATABLE);
if(userdata == NULL)
	return luaL_typeerror(L, 1, WINDOW_METATABLE);

The luaL_checkudata function will return NULL if the one of the following conditions is true:

  • the type of the value at the given stack index is not of type full userdata
  • the metatable of said full userdata is not the metatable passed in as the second argument

Due to the fact that luaL_checkudata doesn’t raise an unexpected type error on its own, we end up doing so ourselves by calling the luaL_typeerror function.

Why didn’t we explicitly cast the userdata to window_userdata_t *, is that a typo? No, in C one doesn’t have to cast to and from void *, due to the fact that such casts are considered to be implicit.

The question that we are faced with yet again is when to use one versus the other?

I’d say, go with light userdata when you want to do explicit and manual resource management , otherwise go with full userdata especially when garbage collection is needed or is desirable.

Personally, I prefer to use simple opaque integer identifiers, instead of userdata of any kind, except when I need upvalues, in which case light userdata can come in very handy, but more on that later down the line.

typedef union
{
	uint64_t opaque;
    struct
    {
		uint64_t type     : 8;
        uint64_t reserved : 24;
		uint64_t index    : 32;
	};
} id_t;

int create_window(lua_State *L)
{
    const char *title;
    int w, h;
    uint32_t flags;
    id_t id;

    title = luaL_checkstring(L, 1);
    w = luaL_checkinteger(L, 2);
    h = luaL_checkinteger(L, 3);
    flags = luaL_checkinteger(L, 4);

    id = os_window_create(title, w, h, flags);

	lua_pushinteger(L, id.opaque);
    lua_pushnil(L);
    return 2;
}

int close_window(lua_State *L)
{
    id_t id;

  	id.opaque = luaL_checkinteger(L, 1);

    os_window_close(id);
    return 0;
}

This approach also reduces the need to litter explicit and verbose error handling everywhere, because you are always guaranteed to get an id.

In case of any errors you just get an id of zero, which is then safe to pass in as an argument to any related functions and is guaranteed to result in a no-op.

On a side note, I just realized the fact that I never brought up how to handle optional arguments.

int create_window(lua_State *L)
{
    const char *title;
    int n, w = 640, h = 480;
    uint32_t flags = 0;
    id_t id;

    title = luaL_checkstring(L, 1);

    n = lua_gettop(L);
    if(n > 1)
    {
    	w = luaL_checkinteger(L, 2);
    	h = luaL_checkinteger(L, 3);

        if(n > 3)
    		flags = luaL_checkinteger(L, 4);
    }

    id = os_window_create(title, w, h, flags);

	lua_pushinteger(L, id.opaque);
    lua_pushnil(L);
    return 2;
}

The lua_gettop function can be used to get the number of elements pushed into the stack. We can use this information to determine how many arguments have been pushed into the stack when our function was called.

It’s really as simple as that. Personally, I also like to use lua_gettop to ensure that the function leaves the stack in a clean state, especially after performing a lot of operations that alter the stack in one way or another.

int table_map(lua_State *L)
{
    int n;

    n = lua_gettop(L);

    /* call functions that operate on the stack */

    assert(lua_gettop(L) == n);

	/* push any return values on the stack */
    return 1;
}

In other words, stack hygiene is essential and can save you from a myriad of weird, subtle and hard to debug issues that might arise when not practiced and taken as seriously as one should.

None of the bindings that we ended up implementing and looking at worked with any global state and it’s high time to change that considering that in any game and app one will have some manner of global state or shared context of some description to work with.

typedef struct
{
    bool running;
    bool vsync;
} game_t;

int game_set_vsync(lua_State *L)
{
    game->vsync = luaL_checkboolean(L, 1);
    return 0;
}

Isn’t the use of global variables static or otherwise considered bad practice? The answer to that question is yes, however not always.

Two particular scenarios come to mind, when in my opinion it’s perfectly fine to use global variables.

Case one, they are static, confined to a single compilation unit (read .c source file) and thead-safety has been considered and handled as necessary (i.e locking read/write via a mutex).

Case two, they are not static but marked as const and extern-ed as such via a declaration in a header file.

However, the use of non static and non const global variables that can be changed from any compilation unit should be avoided at all cost, unless you are a time traveler who also happens to be coding in C like it’s the great year of 1989.

What if the state is not a static global variable? This is where the so called upvalues that I mentioned above come into play.

But, what are these mysterious upvalues exactly?

In simplest terms they are akin to local variables accessible within the confines of a closure.

function create_id_generator()
	local id = 0

    return function()
    	id = id + 1
        return id
    end
end

local id = create_id_generator()
print(id())
print(id())

In the example above you can think of the local variable id as the upvalue of the anonymous function (closure) being returned.

This would be roughly equivalent of a local static variable declared within a function in C.

So let’s rewrite game_set_vsync by using an upvalue to a pointer to our game state.

typedef struct
{
    bool running;
    bool vsync;
} game_t;

int game_set_vsync(lua_State *L)
{
    game_t *game;

    game = lua_touserdata(L, lua_upvalueindex(1));
    game->vsync = luaL_checkboolean(L, 1);

    return 0;
}

We could also check for the type of the upvalue, but this is largely unnecessary in most cases.

luaL_checktype(L, lua_upvalueindex(1), LUA_TLIGHTUSERDATA);

Another useful fact to be aware of when it comes to upvalues is that they do not live on the actual stack and that the lua_upvalueindex function is just a convenience helper macro that returns a pseudo-index.

#define lua_upvalueindex(i)	(LUA_REGISTRYINDEX - (i))

Speaking of closures, let’s take a quick detour and look at the implementation of iterators.

local function odd()
   	local f = function(t, i)
		i = i + 1
        local v = t[i]
        while v do
			if v & 1 == 1 then
                return i, v
            else
                i = i + 1
                v = t[i]
            end
        end
    end

	return function(t)
    	return f, t, 0
    end
end

return odd()

And now let’s see the iterator in action.

local odd = require "odd"

local t = { 33, 32, 13 }
for i, v in odd(t) do
    print(i, v)
end

This will iterate over all the elements of the array, but will yield only when it happens to encounter an element that is odd.implemention

1	33
3	13

The expression v & n == 1 is equivalent to v % n == 1 but only when n is power of two.

Iterators compatible with the in keyword always return a triple, in our case return f, t, 0.

The iterator closure that is called on every iteration, the table that is being iterated on and finally the initial value of the current index.

Closures of course can also be created from C by the means of lua_pushcclosure, but we’ll talk more about that later.

Now that the deep dive into writing bindings has been more or less completed, it is time to check out how to actually expose them in order to make them callable from the Lua.

The first and obvious approach is to just set them in the global environment, by using the reverse of what we did when looking up functions as illustrated in the Calling Lua from C section above.

Except that instead of getglobal, getfield, getttable, rawget you’d end up using the sister setter family of functions, namely setglobal, setfield, settable and rawget respectively.

Not going to go through absolutely all the permutations yet again, I am sure that you get the basic idea behind what I am driving at.

int create_window(lua_State *L);

lua_pushcfunction(L, create_window);
lua_setglobal("create_window");

Exposing a C closure with an upvalue isn’t much more complicated either.

int create_window(lua_State *L);

game_t *game = /* game state */;

lua_pushlightuserdata(L, game);
lua_pushcclosure(L, game, 1);
lua_setglobal(L, "game_set_vsync");

What about exposing some sort of an integer constant? You got a taste of this already when we talked about exposing options and flags.

#define GAME_VERSION 0x100

lua_pushinteger(L, GAME_VERSION);
lua_setglobal(L, "GAME_VERSION");

In most cases you will very likely have more than one function to expose, and just so it happens that Lua has a helper function that comes to our rescue.

I am talking about luaL_setfuncs of course, which we also used to set the metamethods on our metatable above.

static const luaL_Reg window_funcs[] = {
    { "create_window", create_window },
    { "close_window", close_window },
    { NULL, NULL }
};

lua_pushglobaltable(L);
luaL_setfuncs(L, window_funcs, 0);
lua_pop(L, 1);

An important detail to call out is the { NULL, NULL } sentinel value, which is used to designate the end of the static array of function definitions.

The third argument of luaL_setfuncs indicates the number of upvalues that should be pushed for every function defined in the array, which is zero in this particular case due to the fact that the functions in question do not need or use an upvalue.

However, if they did then we’d push the light userdata and set the third argument to 1 like it can be observed in the example below.

static const luaL_Reg window_funcs[] = {
    { "game_set_vsync", game_set_vsync },
    { "game_set_running", game_set_running },
    { NULL, NULL }
};
game_t *game = /* game state */;

lua_pushglobaltable(L);
lua_pushlightuserdata(L, game);
luaL_setfuncs(L, window_funcs, 1);
lua_pop(L, 1);

luaL_setfuncs will pop the upvalues of the stack, leaving only the table at the top, which is why we pass 1 instead of 2 to lua_pop.

Nothing is stopping us from rolling our own luaL_setfuncs and luaL_Reg if that’s what we fancy.

#define _STRINGIFY(x) #x
#define STRINGIFY(x) _STRINGIFY(x)
#define SCRIPT_FUNC(f) { STRINGIFY(f), f }
#define SCRIPT_FUNC_NULL { NULL, NULL }

typedef struct
{
    const char *name;
    lua_CFunction func;
} script_func_t;

static const script_func_t window_funcs[] = {
    SCRIPT_FUNC(game_set_vsync),
    SCRIPT_FUNC(game_set_running),
    SCRIPT_FUNC_NULL
};
game_t *game = /* game state */;

void script_set_funcs(
	lua_State *L,
	const script_func_t *func,
    game_t *game
)
{
    lua_pushglobaltable(L);

    for(; func->name != NULL; func++)
    {
 		lua_pushlightuserdata(L, game);
        lua_pushcclosure(L, func->func, 1);
        lua_setfield(L, -2, func->name);
    }

    lua_pop(L, 1);
}

script_set_funcs(L, window_funcs, game);

lua_CFunction is just convenience function pointer typedef as one would expect.

typedef int (*lua_CFunction)(lua_State *L);

If the use of the { NULL, NULL } sentinel is still giving you heavy anxiety and cold sweats, let’s re-implement script_set_funcs without it in order to ease your pain.

#define _STRINGIFY(x) #x
#define STRINGIFY(x) _STRINGIFY(x)
#define ARRAY_SIZE(x) (sizeof((x)) / sizeof((x)[0]))
#define SCRIPT_FUNC(f) { STRINGIFY(f), f }

typedef struct
{
    const char *name;
    lua_CFunction func;
} script_func_t;

static const script_func_t window_funcs[] = {
    SCRIPT_FUNC(game_set_vsync),
    SCRIPT_FUNC(game_set_running)
};
static const size_t window_funcs_size = ARRAY_SIZE(window_funcs);
game_t *game = /* game state */;

void script_set_funcs(
	lua_State *L,
	const script_func_t *func,
    const size_t size,
    game_t *game
)
{
    lua_pushglobaltable(L);

    for(const script_func_t *end = func + size; func != end; func++)
    {
 		lua_pushlightuserdata(L, game);
        lua_pushcclosure(L, func->func, 1);
        lua_setfield(L, -2, func->name);
    }

    lua_pop(L, 1);
}

script_set_funcs(L, window_funcs, window_funcs_size, game);

Better than a healthy dose of XANAX on a Friday night, right? I certainly hope so.

The second approach we could take, which I am sure you guessed already is to group these functions into modules and then expose those modules. This is what the Lua standard library is doing.

static const luaL_Reg script_window_module_funcs[] = {
    { "create", create_window },
    { "close", close_window },
    { NULL, NULL }
};

int script_window_module(lua_State *L)
{
    luaL_newlibtable(L, script_window_module_funcs);
    luaL_setfuncs(L, script_window_module_funcs, 0);
    return 1;
}

static const luaL_Reg script_modules[] = {
    { "window", script_window_module },
    { NULL, NULL }
};

void script_set_modules(lua_State *L, const luaL_Reg *module)
{
    for(; module->name != NULL; module++)
    {
        luaL_requiref(L, module->name, module->func, 1);
        lua_pop(L, 1);
    }
}

script_set_modules(L, script_modules);

The first thing to talk about here is the luaL_newlibtable macro.

#define luaL_newlibtable(L, l) \
	lua_createtable(L, 0, sizeof(l) / sizeof((l)[0]) - 1)

It creates a table and preallocates enough memory for the number of functions we intend to set in the table. Notice how it accounts for the { NULL, NULL } sentinel value, hence the -1 at the end of the expression.

The second thing to bring forth is the proverbial fourth argument of the luaL_requiref function which is set to 1 (true), which in turn means that each module will also be set in the global environment table.

asset(window.create("Window", 800, 600, WINDOW_FULLSCREEN))

If we set this to 0 (false) then it is necessary to explicitly require the module on the Lua side before it can be used.

local window = require "window"
asset(window.create("Window", 800, 600, WINDOW_FULLSCREEN))

It should be crystal clear to you by now that pretty much everything can be implemented in more than one way, with each implementation having their own pros and cons depending on what you need.

So, global functions or modules? I’d say go with global functions if what you are doing is more akin to data-driven configuration than anything else, otherwise go with modules.

The End

While this turned out to be a much more comprehensive guide of some sorts than what I initially had in mind, it still only manages to scratch the surface when it comes to a great many aspects.

Please take everything outlined here with a grain of salt and do not consider it as some authoritative manual to be followed to the letter, or else all hell will break loose and the four horsemen of the apocalypse will be unleashed upon the world.

To learn more and dive into some of the specific details about Lua, please take a look at the excellent Lua Reference Manual and Programming in Lua.

Until next time, please remember to always, and I mean absolutely always to pop() your pushes() or to push() your pops(), I always forget which way it is.

Anyways, take whatever happens to resonate more with you and your own personal style.

https://mihail.co/2022/12/04/notes-on-lua/
2022 November Retrospective
Show full content
2022 November Retrospective

I accumulated quite a lot of repositories on GitHub over the years. Many are forks of ancient versions of libraries, which some people still tend of find and then attempt to use. Which of course is not a good idea for a number of reasons, but what can one do?

Well, one could go ahead and clean them up. Which is exactly what I started doing this month. There’s still ways to go and more to nuke, but the process has started.

If I had a dollar for every email I have from someone trying to compile or use one of these ancient relics I would have been the one to buy Twitter.

By going through the list of repositories during the cleaning process, I also happened to stumble upon a few very old personal gems of mine that I have totally forgotten about.

Qetesh

Qetesh is a tiny editor that let’s you use Egyptian Hieroglyphs.

image-20221201134748703

It can save/load from local storage and export as an image.

You can find it over here.

Russian Flag Photo Filter

I know that given the current happenings and goings of the world this might not be the right time and place to resurface this in any capacity, but nonetheless I found it funny that this just laid there dormant for so many years.

image-20221201135636924

You can take a look at it over here.

https://mihail.co/2022/11/30/2022-november-retrospective/
2022 October Retrospective
Show full content
2022 October Retrospective

This glorious month I spent some more time working on Niva, the experimental 2D graphics playground that I mentioned back in april, if I am not entirely and completely mistaken.

There is still a whole lot of the Cairo/Pango + some GTK API to expose to Lua, but it’s getting there.

One of the things I focused on in particular was getting some of the Pango API exposed to Lua. The results of which you can witness in the GIF below.

preview

Speaking of fonts, the font you can see in the GIF above is the excellent Yulong font.

local w, h = canvas_current_size()
local num_segments = 3
local rd = 256
local sin = math.sin
local cos = math.cos
local sqrt = math.sqrt
local sprintf = string.format

register_font("examples/fonts/Yulong-Regular.ttf")
set_font(sprintf("Yulong Bold %d", 192))

function draw(w, h, rd, num_segments)
    local xc = w >> 1
	local yc = h >> 1
    local theta = 2 * math.pi / num_segments
    
	local s = sprintf("%02d", num_segments)
	local tx, ty = measure_text(s, rd, rd)

    clear(1, 1, 1, 1)
    
	set_color(0, 0, 0, 1)
	text(tx + 4, ty + 4, s)
	fill()

	set_color(1, 0, 0, 1)
	text(tx, ty, s)
	fill()

	set_color(0, 0, 0, 1)
	set_line_width(2)
	grid(0, 0, w, h, rd, rd)

	set_color(1, 0.1 * theta, 0.4 * theta, 1)

	for i=0,num_segments-1 do
		local x = xc + cos(i * theta) * rd
		local y = yc + sin(i * theta) * rd
		line_to(x, y)
	end
	fill()

	set_line_width(num_segments * theta)
	set_color(0, 0.4, 0.4 * theta, 1)

	local l = rd * 0.5
	for i=0,num_segments-1 do
		local xx = cos(i * theta) * rd
		local yy = sin(i * theta) * rd

		local x = xc + xx
		local y = yc + yy

		local invlen = 1.0 / sqrt(xx * xx + yy * yy) * l
		local dx = xx * invlen
		local dy = yy * invlen

		move_to(x, y)
		line_to(x + dx, y + dy)
	end
	stroke()
end

draw(w, h, rd, num_segments)

if argv and argv[1] == "--preview" then
	canvas_current_preview()
else
	assert(canvas_current_save_to_png("build/preview.png"))
end

As for Pango, the only time I used it was when I built my terminal emulator called Marmota, but in that particular instance it was nothing more than parsing a font description and then passing it to libvte. No actual drawing on a proverbial canvas or anything.

One particularly nasty thing with Pango is the inability to use custom user defined fonts without doing any hacks.

In order to show you what I mean by this, please take a look at the implementation of the register_font function used in the Lua script above.

int nva_lua_canvas_register_font(lua_State *L)
{
    bool result;
    const char *filename;

    filename = luaL_checkstring(L, 1);

    result = FcConfigAppFontAddFile(
        FcConfigGetCurrent(),
        (const FcChar8 *) filename
    );

    lua_pushboolean(L, result);
    return 1;
}

Oh yeah, one has to abuse the fontconfig API in order to make the custom font available for Pango to be able to pick up and use.

pango_font_description_from_string("Yulong Bold 16")

I don’t want to end on a bad note, but it’s a mess and makes Pango totally unsuitable for anything portable.

One might say that it’s fine, since Pango is heavily tied to the GTK and Gnome ecosystems; which is true of course, but on the other hand GTK fancies itself as a portable toolkit of sorts. Oh well.

https://mihail.co/2022/10/31/2022-october-retrospective/
2022 September Retrospective
Show full content
2022 September Retrospective

September came and went like the wind and here I am writing this tiny retrospective up as per usual.

printf vs. debugger debacle

The printf vs. debugger debacle got opened again. It’s like a wound that just doesn’t want to heal for some reson, I guess?

I feel like this time, John Carmack openly talking about it on Lex Friedman’s podcast contributed to it in some way or another, but nonetheless the Twitter-verse was more than happy to jump on the opinion bandwagon once more.

It should go without saying that I am part of the printf camp, and not because I hate debuggers with a passion, or because there aren’t any good debuggers on Linux, or anything of the sorts, but rather the very simple fact that printing something in any format I want will always beat a debuggers’ built-in watch stack/variable capabilities.

And, the second reason is that printf debugging is much more convenient in many cases compared to the following ritual using a debugger:

  1. set the initial breakpoint
  2. launch the debugger
  3. run & step around while watching the stack/variables
  4. profit?

Now, I do understand that if you got a large project, written in a terrible language like C++ (orthodox C++ is still plenty terrible!), coupled with a terrible and slow build system, then having to recompile the project every single time can be very very time consuming, in which case even with the additional steps, using a debugger will make more sense and will undoubtedly be faster. But this is not a scenario that I personally have to deal with, thus using a debugger is simply more trouble than its worth for me.

And, yes I did use the Borland/Turbo Pascal & C/C++ IDEs in my youth which had a built-in and very convenient debuggers. It still made no real difference to me.

turboc

One additional thing that came to light during this debacle that I just cannot comprehend, and that is the an argument that goes something like this:

“Oh, I must use a debugger, because whenever I have to work on a new code base, I heavily rely on a debugger in order to understand said code base”.

Biggest load of bull that I ever heard in my life, period! Well, if you are part of the club that uses this argument, you should seriously reconsider your choice of career, or perhaps it’s time for you to take your leave and retire. Pick up carpeting or gardening maybe?

And, that will be the last thing I’ll say about this for now. Y’all just have to agree to disagree wit me when it comes to this subject.

OLEN Games Toolkit

This month yet again I continued laying down the foundations for the OLEN Games Toolkit and focused on figuring out how to implement error handling.

When one thinks about error handling, without involving so called exceptions that are known to cause permanent brain manage after prolonged use, one usually will land on something that looks like the snippet below:

enum
{
	OLN_RESULT_SUCCESS,
    OLN_RESULT_OUT_OF_MEMORY,
    OLN_RESULT_INVALID_ARGUMENTS
};

int oln_init(const int argc, const char **argv);

int result = oln_init(argc, argv);
if(result != OLN_RESULT_SUCCESS)
{
   // ...
}

This approach of course is what led to the now rather infamous ERROR_SUCCESS, which should be familiar to the true old timers like myself.

In addition, this approach usually will have an auxiliary helper function which allows the user to turn the error code into an actual error message.

const char *oln_result_message(int result)
{
    switch(result)
    {
        case OLN_RESULT_OUT_OF_MEMORY:
            return "out of memory";
        // ...
    }

    return "no error";
}

// ...

oln_log_printf(
	OLN_LOG_TYPE_ERROR,
    "error: %s",
    oln_result_message(result)
)

Another variation on this theme is to use a global last error, which is usually thread local to store the last error in, which then can be retrieved when necessary.

oln_error_set(ERROR_SUCCESS);

// ...

int err = oln_error();
if(err == ERROR_SUCCESS)
{
    // ...
}

Some take this further and have a “stack” of last errors, like OpenGL, while others sometimes end up combining these approaches, a prime example of this would be the Win32 API.

What all these have in common is the fact that they all rely on specific and unique error codes, which is fine if you are writing a specialized API of some description, but not ideal for something more akin to a framework of sorts. Why? Because one has to make sure that user defined error codes are unique, and do not collide with anything provided by the framework itself, which very easily can turn into a nightmare, especially more so when the code base is modular and a certain “bring your own” kind of an attitude is encouraged.

There are various ways to mitigate this. The Win32 API for instance uses one bit out of the 32 bits of the error code to indicate that the error code is a user defined one.

So, let’s take a look at what I ended up with and dive into the nitty-gritty details of the matter at hand.

typedef struct
{
    oln_opaque_t opaque;
} oln_error_t;

#define oln_error_make(message) \
	(oln_error_t) { .opaque = (oln_opaque_t) message }

// ...

const oln_error_t *oln_error_invalid_arguments = &oln_error_make(
    "invalid arguments"
);

bool oln_init(int argc, const char **argv)
{
    if(argc < 2)
    {
        oln_error_set(oln_error_invalid_arguments);
        return false;
    }

    return true;
}

// ...

if(!oln_init(argc, argv))
{
    const oln_error_t *err = oln_error();

    oln_log_printf(
    	OLN_LOG_TYPE_ERROR,
		"init failed: %s",
		oln_error_message(err)
    );

    // ...
}

Calling oln_error is guaranteed not to return a NULL pointer, and once called it will also clear the last error. Therefore, calling it again will return an error with its message set to no error.

What about checking for specific errors? Well, one can just compare the extern globally defined constant compound error literals.

extern const oln_error_t *oln_error_inalid_arguments;

if(!oln_init(argc, argv))
{
    const oln_error_t *err = oln_error();
    if(oln_error_is(err, oln_error_invalid_arguments))
    {
        // ...
    }

    // ...
}

Isn’t this a bit verbose? Yes it can be. In order to get around this, one can also just pass in NULL to oln_error_message in order to grab the latest error message without the need to call oln_error first. Useful in cases where all one wants is the error message itself.

if(!oln_init(argc, argv))
{
    oln_log_printf(
    	OLN_LOG_TYPE_ERROR,
		"init failed: %s",
		oln_error_message(NULL)
    );

    // ...
}

Okay, but what about the case, when it’s simply not practical or desirable to define a constant compound error literal for some reason or another?

// sets the internal message pointer to the
// provided string literal
oln_error_set_message("failed to open file");

// sets the internal message pointer to the
// provided pointer; this means that this pointer
// must remain valid outside of the current "scope"
oln_error_set_external_message(strerror(errno));

// makes an internal copy of the formatted (sprintf) message
// this doesn't allocate from heap, it will simply truncate
// the message if it happens to be longer than 4096 characters
oln_error_set_formatted_message(
    "failed to open `%s' for reading",
    filename
);

Furthermore, oln_error_set_formatted_message can also be used to wrap an existing error in order to provided more context when bubbling an error up the chain.

oln_error_set_formatted_message(
    "failed to open `%s`: %s",
    filename,
    oln_error_message(NULL)
);

And that’s about it. I feel like this offers enough flexibility without the need for error codes and rather obscure non intuitive return values. Time will tell if this was the right call, or if I end up regretting it once a non trivial amount of code has been written using this style.

https://mihail.co/2022/09/30/2022-september-retrospective/
2022 August Retrospective
Show full content
2022 August Retrospective

This month I ended up focusing some of my spare time on laying down the foundations for the OLEN Games Toolkit, and taking Ghidra on test-drive of sorts.

OLEN Games Toolkit

The language of choice is C of course. Which shouldn’t come as a surprise to anyone who happens to know anything at all about me. C11 to be more precise, which should be relatively well supported and implemented at this point by all major compilers.

In order to give you a taste of where I am headed with this, let me give you a concrete example of how the API looks and feels like.

Let us take a look at the implementation header below.

#pragma once

#include <oln/foundation/types.h>

typedef struct
{
    uintptr_t opaque;
} oln_file_t;

#define oln_file_is_open(self) ((self)->opaque != 0)

oln_file_t oln_file_open_read(const char *filename);
size_t oln_file_read(oln_file_t *self, void *buf, size_t size);
void oln_file_close(oln_file_t *self);

Please note how there is no #ifdef OLN_FILE_H guard and #pragma once is used instead.

Also, there are no system wide includes in this header file.

The only include is <oln/foundation/types.h> which contains the following:

  • base type definitions like uintptr_t or size_t
  • helper macros like the quintessential OLN_CONCAT(a, b)
  • preprocessor defines like OLN_WINDOWS for compile time platform, architecture, etc. detection

In other words, the bare minimum, which keeps things fairly lightweight and easy on the eyes.

And now, let us awe a gander at the actual implementation:

#include <oln/foundation/file.h>
#include <stdio.h>

oln_file_t oln_file_open_read(const char *filename)
{
	return (oln_file_t) { .opaque = (uintptr_t) fopen(filename, "rb") }; 
}

size_t oln_file_read(oln_file_t *self, void *buf, size_t size)
{
	return fread(buf, sizeof(uint8_t), size, (FILE *) self->opaque);
}

void oln_file_close(oln_file_t *self)
{
    fclose((FILE *) self->opaque);
}

So, what do we see here? First of all, we include our file.h header first, and then any system wide headers, in this case only <stdio.h>. Which means that system wide types, etc do not leak outside of the implementation scope.

Second, we cast the opaque FILE * pointer we get from fopen to an opaque uintptr_t. But wait, why are we returning by value? Isn’t that one of the 3 cardinal sins of programming in C? Yes and no. In this particular case, there’s absolutely no difference, since both FILE * and uintptr_t are of the same size and will fit in one register.

As a bonus, this also gives us some extra type safety compared to a good old fashioned regular opaque pointer.

And, the last thing to note is how we cast this opaque uintptr_t back to the opaque FILE * pointer as needed.

Last but not least, let’s take a look at how one would use this API below:

#include <oln/foundation/types.h>
#include <oln/foundation/log.h>
#include <oln/foundation/file.h>

int main(int argc, char *argv[])
{
    oln_file_t file;
    size_t n;
    uint8_t buf[4];
    
    if(argc != 2)
    {
        oln_log_printf(
        	OLN_LOG_TYPE_ERROR,
            "usage: %s file",
            argv[0]
        );
        return -1;
    }
    
    file = oln_file_open_read(argv[1]);
    if(!oln_file_is_open(&file))
    {
        oln_log_printf(
        	OLN_LOG_TYPE_ERROR,
            "failed to open `%s' for reading",
            argv[1]
        );
        return -1;
    }
    
    n = oln_file_read(&file, buf, sizeof(buf));
    if(n != sizeof(buf))
    {
        oln_log_printf(
        	OLN_LOG_TYPE_ERROR,
            "failed to read %lu bytes (got %lu bytes instead)",
            sizeof(buf),
            n
        );
        oln_file_close(&file);
        return -1;
    }
    
    oln_file_close(&file);
    return 0;
}

Not bad at all? Right? I think so too. Obviously, nothing is set in stone just yet, but this looks nice, clean and extend-able as far as I am concerned.

One thing I am still on the fence about is whether I should call it std instead of foundation.

#include <oln/std/types.h>
#include <oln/std/log.h>

I will be posting more about this as things continue to evolve in the coming weeks and months.

Ghidra

Reverse engineering applications, games and various file formats used to be one of my favorite pastime activities in my younger years.

Whenever I wouldn’t be coding, I would most certainly be looking for patterns with a hex editor in some file of a game or an application.

While I don’t wander into this territory quite often these days, I still find myself quenching my thirst for patterns every blue moon or so.

In the early days, some of my tools of trade were W32Dasm and HView (quite typical at the time), then later in the early 00s Olly Debugger, which was an absolute god-sent for me personally. A true game changer and in many ways still undefeated when it comes disassembling and/or modifying (patching) 32-bit executables on windows in my humble opinion.

A year ago or so, I started doing some digital clean-up (I am a digital hoarder to a certain extent) and I stumbled upon some remnants or perhaps I should relics of my reverse engineering era, which turned the fire in my belly up a notch.

Due to this, I did a quick search to see what are people using these days for dissembling executables and that’s how I stumbled upon the almighty Ghidra, developed by the NSA.

I took a quick look at it and it seemed like everybody and their grandmother is using, especially in so called cyber-security circles (whatever that means!).

Then of course, in my usual way got side-tracked and never really ended up doing a deep dive so to speak, until earlier this month when I finally decided to give it a go.

ghidra

One of the most valuable features of Ghidra is the ability to see a decompiled view of the disassembly, which makes things a lot easier to parse and skim through. Reading unannotated assembly produced by a compiler is vastly different experience from reading hand crafted assembly, which makes the decompiled view an absolute gem.

Another great feature is the ability to “parse C source code” and auto-determine structures, types, etc, however it’s not as easy as it might sound to please the “C parser” plug-in, therefore most of the time you’ll find yourself that you have to massage things by hand, etc. This is one of the areas that I’d love to see some improved.

Speaking of things to improve, the experience of patching (entering assembly instructions) could also use some more love compared to the absolutely amazing experience provided by Olly Debugger.

I will not go into some of the other features, but all in all it is truly a one stop shop when it comes to both analyzing and patching binaries.

Don’t want to make any promises just yet, but I might end up doing a series of streams / videos focusing on reverse engineering, as I already set my eyes on more or less the perfect target for such a thing.

https://mihail.co/2022/08/31/2022-august-retrospective/
2022 July Retrospective
Show full content
2022 July Retrospective

July was a very very unproductive month. I feel like it’s always my most unproductive month of the year when it comes to my personal projects. Please do not quote me on this. On the other hand, I can always just blame it all on the global warming and the general goings of the world, as it is very customary to do so these days.

Not much to report! Let’s see what the next month will bring.

I’ll leave you with something cool, a book from 1910 – titled Calculus Made Easy.

https://mihail.co/2022/07/31/2022-july-retrospective/
2022 June Retrospective
Show full content
2022 June Retrospective

Here we are 6 months into the year. Time truly really flies by and I am happy to bring you another dose of nutritious episode of pure and elemental yak-shaving. Getting there, slowly but surely.

A few years ago, I wrote and released a Vim plug-in called fastopen.vim - a quick file opener. This was before the days of Vim having all the hot and shiny features like asynchronous jobs, timers and pop-up menus.

Screenshot

It relies on the excellent dmenu to display the always-on-top drop-down menu.

Now with the advent of Vim 9 released just a few days ago, I felt like it’s about time to take it to the next level and create another such plug-in while also leveraging vim9script, which is more or less a faster and in some ways saner version of good old vimscript.

There will be a lot of opinions about the merits of vim9script in the coming months.

nextopen

Will wrap it up and release it most likely in the next couple of weeks, as I have the basic functionality all sketched up and functional already.

In other news, I bought an ASUS Vivobook Flip 14 that came bundled with Windows 11 Home Edition. I was super duper curious to see if the rumors were true about Microsoft not letting you create a local account without pulling some stunts.

And, indeed it is true. First of all they do force you to hook up to a WIFI during the initial setup process and as a result you no longer get the option to create a local account.

The only way I managed to get around this is by restarting my router and then going back and forth in the setup in order to pick up the fact that it couldn’t connect anymore and voila, the ability to create and use a local account became available.

I don’t think that it even has to be called out that this is an absolutely despicable practice.

As far as the actual hardware goes, I find it amusing that we are no longer calling them “Tablet PCs”, like we used in the good old days, when of course I could never afford one. I guess the stigma is still up in the air, and besides calling them “Flipbooks” is a lot more hipster. It’s still nowhere near cheap, but a lot more affordable and has more than enough horsepower. The “new” Intel GPU, as always will not be the most stellar and shiniest thing on the block, but I feel like it’s a good compromise for such a device and it’s light years ahead of previous generations of Intel GPUs.

On more somber note, in my May retrospective, I was lamenting about the state of libvte and lo and behold more absolute madness hits the news, this time about GTK5.

This is clearly the result of none of these projects having a Benevolent Dictator For Life. Not going to complain more about this, but my cup keeps filling up and it’s getting to the point where it will start overflowing. We can’t talk about the “year of the Linux desktop” if at every step the underlying foundational blocks end up sabotaging any and all progress, no matter how minuscule.

https://mihail.co/2022/06/30/2022-june-retrospective/
2022 May Retrospective
Show full content
2022 May Retrospective

May, didn’t turn out to be as a productive month as I would have hoped when it comes to my personal projects, but still managed to get some stuff done.

Decided to take Niva a little bit further than I initially thought that I would, and ended up experimenting with a couple of features when it comes to the “preview” mode, which should allow one to build interactive more interactive “previews”.

In the screenshot below you can see a small ray-casting experiment.

image-20220601122104713

In other news, I also started looking into libvterm, because I am growing more and more frustrated with libvte and its maintainers’ rather questionable decisions.

Fun fact: libvterm is also the “library” that vim uses for the built-in terminal that comes with vim 8.x and up.

Obviously, rolling with libvterm and building another terminal emulator from scratch would add yet another distraction in terms of side projects, but I feel like it just has to be done for my own peace of mind.

That is all i got to share this month. Let’s see what June brings. I can’t believe that we are 6 months into the year already.

https://mihail.co/2022/05/31/2022-may-retrospective/
2022 April Retrospective
Show full content
2022 April Retrospective

The days, weeks and months are just flying by. Perhaps it’s just a sign that I must be getting old.

At any rate, I find that the monthly retrospectives do bare fruit, even more so than I would have thought.

Last month, with all the excitement around Flordle, I forgot to mention another small experiment that I did involving pixel art and bump mapping. The result of which you can enjoy in its full glory in the tiny video below.

One of the advantages of writing your own terminal emulator is that you can add in any feature that you want, without having to deal with anybody else.

As you can see in the screen-shot above, I ended up adding support for background images. Not the most impressive or useful of features but why the heck not.

By the way, I am still on the fence about VTE, due to the absolutely nonsensical decisions taken by its maintainers, and it takes a lot of strength to suppress the need to ditch it and roll my own widget. Please, do not tempt me Satan!

If for any reason you might want the background image seen in the screen-shot above, then you can find it over here.

I also launched a tiny Gemini capsule which can be found over at gemini://gemini.mihail.co. How long will I keep this up and operating? It’s hard to tell at this point in time, we’ll see where the four winds take us in the coming months.

gemini

Does this mean that I finished the small Gemini server that I intended to write in Go and mentioned in my end of the year retrospective? No, of course not. It would have been way to simple to do so.

I ended up rolling something rather unusual and special in a way by using ncat with its built-in --lua-exec support.

$ openssl req -new -subj "/CN=gemini.mihail.co" -x509 -newkey ec \
	-pkeyopt ec_paramgen_curve:prime256v1 -days 3650 -nodes \
	-out certs/cert.pem -keyout certs/key.pem
$ ncat --lua-exec server.lua -l --keep-open -m 32 --ssl \
	--ssl-cert certs/cert.pem --ssl-key certs/key.pem -p 1965
-- server.lua
local f = assert(io.open("index.gmi", "rb"))
local s = f:read("*a")
f:close()

io.stdout:write("20 text/gemini\r\n")
io.stdout:write(s)
io.stdout:flush()

Of course the version I have running is a bit more advanced and can handle arbitrary paths by reading from stdin via io.read and then figuring out the requested resource path from there, but the general idea stays very much the same. I think it’s pretty neat and low maintenance, without having to roll any sort of heavy infrastructure around it in order to support it, which is always a huge plus in my eyes.

I will still come back and finish up the server in Go at some point or another, but do not hold me to it.

What I did say about seemingly random and ad-hoc side projects? Well, I started another one and without going into way too many details too early, it’s an experimental 2D graphics playground.

niva

$ niva preview.lua --preview
-- preview.lua
local w, h = canvas_current_size()
local num_segments = 6
local theta = 2 * math.pi / num_segments
local rd = 256
local xc = w >> 1
local yc = h >> 1
local sin = math.sin
local cos = math.cos

set_line_width(2)
grid(0, 0, w, h, rd, rd)

set_color(1, 0.1 * theta, 0.4 * theta, 1)

for i=0,num_segments-1 do
	local x = xc + cos(i * theta) * rd
	local y = yc + sin(i * theta) * rd

	line_to(x, y)
end

fill()

if argv and argv[1] == "--preview" then
	canvas_current_preview()
else
	assert(canvas_current_save_to_png("preview.png"))
end

-- vim: set ft=lua ts=4 sw=4 sts=4 noet:

In other news, I also started digging into the binary crate file format that is part of USDZ and boy it’s an absolute disaster. I said this a few time, but once I figure it out all and write a dependency free exporter, I will most definitely write up a lengthy post about it. Not much to report just yet!

Oh, I almost forgot. Ended up buying a snowball iCE microphone, which should provide me with some better sound quality, whenever I end up doing another coding live-stream on Twitch.

And, that is the majority of the things that I wanted to report on this month. Not great, not terrible at the same time.

https://mihail.co/2022/04/30/2022-april-retrospective/
2022 March Retrospective
Show full content
2022 March Retrospective

Has been a month already? It does look like it. The weeks are just flying by like no tomorrow!

At any rate, as promised, Flordle, my boot-table and M$-DOS compatible text-mode Wordle clone, has indeed seen the light of day and is fresh out of my proverbial code oven.

flordle

It took me a little bit more time than I expected, but it did turn out fairly well in the end, if you ask my personal and totally biased opinion.

Ended up rolling with 1024 built-in words and the final executable size of 8 kilo-bytes. Could have been smaller if I would have compressed the list of words, but honestly I didn’t feel like it was worth the effort to do so.

When I built and released Floppy Bird, there was one thing that I never ended up fixing before release and that was booting on certain laptops with floppy emulation.

It appears that certain BIOSes (in particular ones shipped with laptops) end up being too smart for their own good and attempt to patch the BPB (BIOS Parameter Block), which of course I didn’t realize or know about. What this means is that, the BIOS ends up overwriting a few bytes after it loads up the boot sector (the first 512 bytes), which in turn messes everything up.

To work around, it’s best to define a BPB or at least reserve some empty space for it at the beginning of the boot sector and then “jump” over it with a “near” (short) jump.

bits 16                           ; 16 bit mode

; 0xEB, 0x76, 0x90
jmp start                         ; jump to start
nop                               ;
times 117 db 0                    ; reserve some empty space for BPB

start:
...

And, BINGO, problem solved! Thanks to this, Flordle should boot-up on most hardware.

Now that Flordle is out there, in the month of April, I intend to focus on the USDZ exporter. Would really like to figure and flesh out the under-document binary crate file portion of it, which is going to a going to be a lot of FUN. /s

Oh, another relatively small thing that I’d like to mention is that I ended up buying a license for Typora.

Typora is a really cool markdown editor and reader that just came out of beta. Even though I am a heavy VIM user, I still enjoy writing some of my markdown in it.

The in-line seamless WYSIWYG editing is really cool. Instant feedback. If I peeked your interest, there’s a 15 day free trial version that you can play around with before you start throwing all your hard earned cash at your monitor.

https://mihail.co/2022/03/31/2022-march-retrospective/
2022 February Retrospective
Show full content
2022 February Retrospective

Yet another month is behind me, but this one had a little bit more of a significance, because it happened to be the month I gracefully did age++ like a true pro that I am.

So, what has transpired? Well, I know that I said that there won’t be too many side-projects to distract me from working on what I ended up calling the OLEN Games Toolkit, BUT and that’s a BIG but …

Well, I decided to build a small boot-able as well as MS-DOS compatible Wordle clone. I know, I know, isn’t it a bit too late for that? Yes, and no, because the hype didn’t die out completely yet. And besides, I really wanted to do something like this yet again, considering that the last time I’ve done it, well it was was ages ago now, back in good old 2014 when I released Floppy Bird, my boot-able Flappy Bird clone.

In other news, also picked up work on the USDZ exporter that I’ve built a proof of concept of late last year. This is written in C#, with no third party dependencies and its primary purposes is to be used within Unity and to be fully compatible with WebGL builds.

Once I have it in a good and usable shape, I’ll write a dedicated post about it, because the USDZ format, let’s just say that it’s one of the worst formats out there. But more on that later, so don’t drop your pants just yet.

Other than that, I also started to make some progress on the foundation library of the OLEN Games Toolkit. Once again, more on that later, when I actually have some juicy technical details to share.

https://mihail.co/2022/02/28/2022-february-retrospective/
2022 January Retrospective
Show full content
2022 January Retrospective

Holy-molly, it’s the end of January. Time truly flies by these days, more so than used to be. At least that’s how it all feels to me; or perhaps I am just getting old and gray, which of course wouldn’t be surprising. Nobody can stay young forever, not even me the great code gardener of the gods.

But on a more serious note, I did promise at least one post per month and here I am fulfilling that promise. I think that doing it more of a monthly- retrospective-almost-newsletter-style might actually work and force me to sit down and write, rather than just keep postponing it indefinitely by finding various excuses.

While January was truly a very very busy month for me work wise, I didn’t get much done on my personal projects; in fact I got done even less than I hoped that I would find the time to do so.

But, I did start the work in earnest so to speak on OLEN Build and started sketching out the OLEN Games Toolchain as a whole as well. There isn’t anything that I could write in length about just yet, but I managed to get the proverbial ball rolling in the right direction. Or at least that’s what I would like to believe.

However, there’s one detail that I will share, even though it’s not going to be the most exciting thing known to man. What is it, you might ask?

Well, I decided to use OpenMP for the parallel-execution aspect of OLEN Build. How well will this turn out to be in practice, that remains to be seen, but even if it turns into an utter disaster, the project is still in a very early state so it should be easy-peasy to swap it out and do it the old fashioned manual way without relying on OpenMP.

In its crudest incarnation, one can imagine something like the code snippet below.

const char *cmds[] = {
	"cc -O2 -c test0.c -o test0.o",
	"cc -O2 -c test1.c -o test1.o",
	...
	NULL
};

...

#pragma omp parallel for shared(cmds) schedule(runtime) ordered
for(const char **cmd = cmds; *cmd != NULL; cmd++)
{
	char *output = execute_cmd(*cmd);
#pragma omp ordered
	puts(output);
	free(output);
}

...

Not too shabby, right? Of course one mustn’t forget to compile with -fopenmp, but that’s a small price to pay.

At any rate, that’s pretty much all I got to share for the time being. See you at the end of next month, which also happens to be the month when I do age++.

https://mihail.co/2022/01/31/2022-january-retrospective/
2021 Retrospective
Show full content
2021 Retrospective

As the 671th day of 2020 is finally drawing to a close, we find ourselves at the turn of yet another page, which promises nothing but misery and sorrow. Crows feeding on thousands of corpses hanging upside down. Oh wait a second, this is the introduction of a rather different post of mine, just unread that please, it’s too late to delete it now.

On a more serious note, let us take awe a gander at the traditional year-in-review commit chart, graciously generated by none other than the mighty GitHub and let the data speak for itself.

The avid readers of this blog, or rather the more observant ones, will immediately notice how number of contributions is significantly lower compared to previous years. And what is the reason for that? Well, the explanation is rather simple I’m afraid, it’s the simple fact that I managed to put in a lot fewer hours into my various side projects.

Those of you who happen to know me personally in some capacity, also will surely know that I always have a number of side projects that I am actively working on at any give time. It is almost like an endless queue of ideas that are yet to be realized and unleashed into the world. That reminds me that I really have to clean up my repositories (144!) on GitHub, because things are getting out of hand.

OLEN Games

While I got a lot less done this past year, I did manage to complete a couple of research cycles when it comes to two of my most important projects of which you heard me babble over the years in various of forms and incarnations.

What am I speaking of? The portable 2D/3D game engine and framework, and more recently the self-contained portable build automation tool designed for projects primarily written in C/C++.

These two bad boys are at the top of my totem pole, and in 2021 I finally managed to figure out the direction, style and general scope of them. My great hope for 2022 is to make some serious headway on both, instead of my traditional style of it’s done, when it’s done.

This is where OLEN Games is going to come into play as an umbrella of sorts, which will let me separate these two (and more!) bad boys from the rest of my experimental work and put them into more of a front-and-center position so to speak.

Having an umbrella like this has the added benefit of simplifying the process of naming things. How do people even name their children? In my case, I will just roll with OLEN Engine and OLEN Build. Tada! Naming crisis graciously averted.

I always tend of blabber on and on about technology, but I am building most of it in order to finally be able to build a proper game or two in the near future. Building an engine, without building any games in-house with it, is a very very dangerous and ill advised thing to do, and I highly recommend anyone against going down that path of treachery, it will not lead anywhere good!

A long overdue face-flit

After many moons of rolling with the same super basic design of this blog, I finally decided that it was about time to give it a well deserved face-lift as well as migrate it back to Jekyll.

A number of years ago I was looking for an excuse to write something non-trivial in Python and the result of that was the static site generator, that I eventually ended up calling doxter.

Once doxter was ready for prime-time I migrated this blog from Jekyll to it. And now the natural question is why migrate away from it and back to Jekyll again? Since that time Jekyll and GitHub made a wide array of changes, and GitHub Pages got even better, therefore I felt like going native again would be good idea.

It remains to be seen if/when I’ll regret this decision in the months to come.

doxter is still out there and perfectly usable. It withstood the test of time, even going from python 2.x to 3.x without any backbreaking changes or fuss, which is isn’t something totally unexpected, considering the fact that I am the second best programmer that has ever lived. I’ll let you discover who is the very first one, but I am sure it has to do with a temple and an operating system of some description or another. What a weird combination, right? Totally!

In other, very much related news, decided to keep the RSS feed alive, despite the fact that many consider RSS to be long dead at this point in time. Of course, if you happen to be reading this in your favorite RSS reader, then the so called facelift that I’ve been babbling about above will make absolutely no sense to you, and perhaps that is how things should be in life, wouldn’t you agree?

Also, I took the time and made the blog more mobile friendly, so it is now enjoyable on your extremely precious mobile phone or tablet, even though it’s highly unlikely that one would be consuming anything I write on this blog that way.

And now without any further ado, let us take a deep dive into the actual projects and experiments that I have hacked on during the course of this rather ghastly past year.

WoWiconify

WoWiconify was the result of some super duper impromptu coding session, without too much thought and thinking put into it, but the results turned out to be fairly decent and interesting.

Let me loop and ease you into what it actually is, and start the conversation off with a couple of images, which I am told they are supposed to be worth not less and not a whole lot more than exactly a thousand words.

Considering this was told to me before NFTs were a thing, I consider it one of the few core truths of our existence.

Input

Output

What is going on? Well, it is a small command line tool that takes an input image and a number (up to a maximum of 512, which is a totally arbitrary limit) of spell icons from WoW (World of Warcraft), and produces an output image that uses those said spell icons to approximate the original input image.

It’s only a handful of lines of C and uses a couple of single file libraries by Sean Barett with no other dependencies.

Considering that this is merely a toy project, I do not provide any pre-compiled binaries, but if you want to check out the source code and perhaps even compile it yourself, then feel free to click right here.

Legend of Grimrock 2: Multiplayer uMod

That is a mouthful isn’t it?

Last year in December the so called uMod support has landed in a special nutcracker beta branch of Legend of Grimrock 2 on Steam, in the form of a Lua source code drop and the ability to execute Lua in an unsandboxed fashion, which allows mods more or less unrestricted access to the main Lua state and can combined with the source code drop can be used to monkey-patch almost anything within the game.

This also allows one to create a completely new game in an entirely different genre. In other words, the possibilities are truly endless in terms of what one could do with it, so long all changes/modifications still abide by the modding and asset usage terms.

I knew about this and I took a quick glance at it right when it was dropped by Petri Häkkinen from Almost Human, but then as the new year came about it completely fell of my radar and forgot about it.

Since I take a yearly sabbatical every year in December, by taking the entire month off, I decided to take another look at it and see if I could leverage it to add some sort of drop-in co-op multiplayer support, which would allow two players to play both the main official campaign of the game as well as the myriad of user created custom dungeons. Obviously this doesn’t exclude the possibility of creating more co-op multiplayer focused dungeons either, it’s just that it must work with existing content.

After a few days of rather intense experimentation I came up with what can be seen in the video below.

I am not going to go into any of the technical details, nor release any of the underlying code just yet. Why? For two very good and perfectly valid reasons, which I am going to unpack below.

First of all, I don’t want to get ahead of myself until I am absolutely certain that I can pull this off in the manner that I just described above.

And second, the nutcracker beta branch of the game that enables uMod support is still kept hush-hush and low profile by the developers for the time being, and I don’t want to unnecessarily rock the proverbial boat just yet so to speak.

Besides, the entire journey will warrant a proper brain-dump type of a technical post, where I intend to take a deep dive into how it was all done, considering that there’s a treasure trove of things to talk about.

Fit for Autopsy

One of my favorite pastime activities is taking an existing code-base and attempting to port it to the web via the means of WASM by leveraging Emscripten.

This year’s victim happened to be Fit for Autopsy, which is a demopack or demo anthology by Fit.

It seemed like a natural fit, pun intended, and was already portable and compile-able on a number of platforms.

Now it did had a few remnants of good old fashioned OpenGL fixed function pipeline in some of the renderer, which I hoped that I could just keep as-is and have the fixed function pipeline emulation present in Emscripten handle.

Well, it kind of worked, but it definitely doesn’t render everything correctly, which means that I’ll have to rewrite some of the renderer, which is not too bad.

You can watch a very much work-in-progress-wasm-port version of the demo called Boy from the demopack over here.

Another WASM/Emscripten related project that I hacked on was a small tile-map editor written in C and using only the 2D <canvas> context to draw everything without utilizing WebGL directly.

Before you ask, for this particular experiment I ended up borrowing the assets from Catacomb Snatch.

I would also really like to port something of a higher or rather bigger caliber like Penumbra Overture or Shadowgrounds, but I have not managed to work up the courage just yet, considering that both of those would require a complete rewrite of their renderer and more before they could be even considered for porting anywhere.

js13kGames

The yearly [js13kGames][js13k] competition is part and parcel of my yearly collection of well defined rituals. This year hasn’t been any different, and I attempted to come up with something that is not totally uninteresting, yet it can fit into a paltry 13 kilo bytes after being compressed.

Unlike other years, I did manage to come up with a fairly decent idea in my honest and humble opinion, but like always didn’t manage to push it over the finish line, due to lack of time, etc. One day!

The idea itself, while not anything super revolutionary, but a tiny procedurally generated space gardening game, where the player has to tend to the plants on the planet and collect enough raw material to open the teleporter, which takes them to the next randomly generated planet. All this while fending off a flying enemy that also wants and likes the raw materials and desperately would like to collect them; also whenever it encounters the player, it will attempt to steal its raw materials as well as water, which is a scarce resource.

Perhaps, I’ll come back to this idea and turn it into reality one day, given that nobody beats me to it before, that is!

QOI

A couple of weeks ago, Dominic Szablewski has announced a completely new image file format called QOI, which needless to say has gotten quite a lot of attention as well as traction among like minded peoples of the internet.

I didn’t want to miss such an historic moment, the birth of a completely new image file format is quite a big event. So, as any good law abiding citizen would, I decided to make my contribution in the form of a so called gdk pixbuf loader, the source code of which can be found over here.

This allows existing GTK applications to load/save and generate thumbnails for images in the QOI image format, which turns the format into more of a first class citizen in the world of applications built with GTK; which is a significant amount of applications in the linux ecosystem.

As this was thrown together in mere three days it definitely needs some rigorous testing, and there might be some bugs lurking in the shadows due to perhaps not handling some corner cases, but all in all it’s usable and gets the job done.

Building loaders is totally under-documented and an obscure process, it made me realize how arcane and in many ways broken this entire system is. It’s to the point of requiring its very own separate and dedicated post in order to fully document and chronicle my experience, but that’s a story for another time.

Gemini on the Go

I mentioned Go during my 2020 retrospective and expressed my desire to write something non-trivial using it in order to be able to evaluate its merits, weaknesses and most importantly where and how it fits into my tool-belt.

Two months ago or so, I stumbled upon the Gemini internet protocol.

Gemini is a new internet protocol which:

  • Is heavier than gopher
  • Is lighter than the web
  • Will not replace either
  • Strives for maximum power to weight ratio
  • Takes user privacy very seriously

What shocked me the most is that I never heard about it until that very moment in time.

Just like with QOI, it’s not an very day event to witness the birth of a brand new internet protocol like this, so once again I wanted to leave my mark and make my contribution.

As a result, I started building a very tiny Gemini server and client combination in the form a command line utility in Go.

Once it’s ready, will use it to power and run my own Gemini capsule, which I intend to host on a Digital Ocean droplet.

This is really the perfect type of an application for Go, and will allow me to delve into some of the flagship features that set Go apart, like Goroutines, networking and so on.

You can expect a dedicated post in which I will wholeheartedly dissect my experience and perhaps even shell out some totally unsolicited advice for good measure.

Aren’t there plenty if existing Gemini servers and clients? Oh, there are plenty, in fact one might go as far as saying that there’s rather healthy and active ecosystem built around it already.

One of my favorite GUI based Gemini clients’ is Lagrange.

The word client might be a misnomer, browser is probably a way better term to describe it.

A great question that you must be asking yourself right now is who exactly might be the target audience of such a niche internet protocol? And that is an extremely good question, which I’ll try to explain in the way I see it.

From where I stand, there are three distinct groups of peeps using Gemini at this point in time.

First, let’s just call the them the old timers, the ones who got to live through the hey-day of BBSes or at least were teenagers or perhaps young adults during those golden days of glory. They always reminisce those days and have an ardent desire to relive them in one way or another. Need not to be said that this group is totally unsatisfied and annoyed by what the so called modern web has become.

The second group, which I proudly include myself in, is the group that missed the BBS days or got only a minuscule taste of them due to timing and/or other reasons and always longed to experience it in its almost raw form.

Last but, not least, the third group is composed of children of the modern world, the outcasts, desperately seeking a quiet place away from the neon lights of the metropolis; for them, something like Gemini is like sanctuary and invokes a feeling that is somewhat weirdly familiar, but totally foreign at the same time.

The great ZIGgurat

Zig is yet another contender in the relatively new and very shiny languages that ought to replace C/C++ in one form or another within our natural life times.

I am not terribly interested or impressed by the language itself. How should I put it best? It’s way too avant-garde for my humble needs and desires when it comes to languages.

However, this is not a critique of the merits or shortcomings of the language itself, but rather the toolchain that powers it under the hood.

As most languages these days Zig leverages the LLVM toolchain (which clang is part of) to do most of the heavy lifting when it comes to bootstrapping and so forth.

As a direct result of this, it comes with a fully functional C/C++ compiler hidden under the hood. Well, hidden until Zig’s creator, a good lad known as Andrew Kelley, decided to pull off the hood and expose it in the form of zig cc.

zig cc is a wrapper over the LLVM toolchain under the hood and provides some additional features on top that aren’t available if one would go with the native toolchain.

This is all fine and dandy, however before one could use it a true drop-in-replacement for gcc and/or clang it still needed at least two more things in my book.

First, it needed ar to be exposed as well in order for one to be able to create static libraries and then a windows resource file compiler, colloquially known as windres in the world of mingw based cross compiler.

ar was eventually exposed as zig ar, which left windres as the last piece of the puzzle.

Once again as a citizen of the free world, I took upon myself the task of adding windres, unfortunately I failed to boostrap the zig compiler itself and after a few days I set it aside and put this project into the back-burner.

There have been a few zig releases since then and I intend to take another stab at it during the course of next year. But once again, I shall not many any premature promises!

Living life on a Vim

Give a man a Vim plugin and you solve one of his problems. Teach a man how to write Vim plugins and he’ll write one to solve every single one of his problems. I don’t remember exactly how that goes, but it should be something that is pretty close to those lines.

I always tell people who are considering ramping up and switching to Vim is that unlike with other editors the entire thing is a process and it cannot happen over night, due to every single one of us having a quite different as well as needs in order to be what we ultimately end up calling productive.

In my any ways it’s as much as about the journey as it is about the destination, in terms of identifying some sort of an inefficiency in your very own workflow and way of using vim and then looking for a way to correct it. Just like with most things that matter in life, there’s always a better way and that happens to be no different when it comes to Vim.

[Bram Moolenaar] the creator and benevolent dictator for life of Vim, happens to have a very good presentation touching upon this very subject of identifying one’s inefficiencies, finding better ways and then turning those into habits.

It’s quite dated at this point, but everything said in there still holds up pretty well to this very day.

You might be thinking that you didn’t sign up for a lecture about Vim when you started reading this post and here I am preaching you like a true evangelist, gospels about effective text editing in Vim.

Fear not, that is not my intention, I just want to give an update or two about about some changes in my own and very personal Vim world.

I made some improvements to my Vim plugin called fastopen, which allows one to fuzzy search and open files within a directory by leveraging dmenu and Git if the user happens to be editing within the confines of a Git repository. With these changes in place the root of the repository is now properly detected no matter from which sub-directory the user might have started Vim to edit a particular set of files.

Ended up improving the Git repository detection, for which of course I rely on the presence of the excellent Git integration Vim plugin called fugitive by none other than the absolute legend himself, talking about Tim Pope of course. He’s probably one of (if not) the most prolific Vim plugin authors out there.

Now that Vim 8 has been out there for a while, I’d like to make an experiment and see if I could use the built-in popup menu support to display the list of files instead of shelling out to an external tool like dmenu, but that’s a experiment for another day.

Speaking of dmenu, it also happens to be yet another utility on my apparently ever growing list of tools that I’d very much like to replace with my own.

Fantastic Urges and How to Control Them

No, this is not a new novel fresh out of the oven by our beloved J.K Rowling, nor it is a campaign by PornHub, but rather me resisting the urge of writing my very own X11 window manager among other things, from scratch no less.

In 2020 I decided that enough is enough and wrote my very own terminal emulator from scratch called marmota. I didn’t go completely off the hook and ended up using vte under the hood, but everything around it in terms of features and capabilities has been built keeping my own particular and not so particular needs in mind.

Why would I do that? I grew sick and tired of the constant breakage, ad-hoc feature addition, removals or simply changes in existing behavior of the existing terminal emulators out there, many of whom do use vte under the hood as well.

So, I’ve been using it without a hitch until earlier this year, when after an update, miraculously my fonts were no longer being hinted properly in the terminal.

At first I though that it must be some distribution level change that has happened and perhaps they re-calibrated the hinting priorities and such and thought that it will get addressed in no time.

However, as time went on, new updates were flowing in but my fonts were in the terminal were still looking absolutely horrendous. Now, I was starting to get suspicious a little bit and after some digging managed to locate the culprit.

It was indeed a change in vte itself, which has been partially reverted, but never ever restored the previous totally sane and valid behavior.

And now here I am contemplating ditching vte and rolling my own, which is a ridiculous thing to do, I admit as much and potentially quite a bit of work, but I am at an age where I can no longer take such absolute nonsense.

And what about writing my X11 window manager? Well, considering that I already rolled my own terminal, might as well ditch my current window manager which happens to be Openbox and roll my own.

That would make me only one or two steps away from my own distribution. I guess we’ll have to live and see what the future holds and where the four winds take us.

Oh, with all the commotion I almost forgot about the glorious day when all of the sudden my GNOME Keyring broke due to a change in the name of security to GLib itself.

With all that said the natural and rather ugly question does rear its ugly head. Can you guess what that question is?

Could 2021 misterously been the year of the Linux Desktop at last?

The answer to that rather most outrageous and ridiculous question according to ancient atrolinux theorists is a resounding and most definite NO.

Let us just leave it at that, shall we? Some things are better to be left unspoken.

GNOME dēlenda est!

P.I.M.P My Desktop

It’s been a while since I made any major changes to the look and feel of my desktop. It is a sign of my age? Perhaps.

After more than a decade I decided to switch my cursor theme from Comix Cursors to Bibata and I’m more than happy to report that I’ve never been happier.

As far as monospaced fonts suitable for coding are concerned, I celebrated my second year anniversary with the rather excellent JetBrains Mono.

The End

This is the end. I do hope that some of this resonated with it and if it did you can find the paid extended version below by subscribing to my onlyfarmers profile.

Not a big fan of new years’ resolutions myself, which is not terribly surprising considering that fact that I am not really a big tea person myself.

Nevertheless, it seems appropriate to end this rather lengthy retrospective with some sort of a list of it-would-be-nice-to-do for the rather ghastly upcoming year:

  • have some showcase-able builds of OLEN Build and OLEN Engine
  • continue working on the Legend of Grimrock 2 multiplayer uMod
  • live stream some coding sessions at least twice a week on Twitch; during the weekend of course
  • upload the VODs of said streams to my YouTube channel in order to start building an audience over there
  • reach a 1000 followers on Twitter
  • write a retrospective post every single month; no excuses or exceptions
  • get married and have quadruplets

Until next time as the great and eternal server once said to me: END OF LINE.

P.S: Make sure that you do not have a patchy log server somewhere in your basement.

P.S.S: If you didn’t get that joke, it is time for you to retire and pick up gardening or carpeting.

https://mihail.co/2021/12/31/2021-retrospective/
2020 Retrospective
Show full content
2020 Retrospective

It’s that time of the year once more, however considering what a ghastly year 2020 was, I will cut the intro short and not antagonize you any further than absolutely necessary. If you were expecting wholesome eulogies, this is not the spot, and you might want to check out medium.

Before getting too much into the weeds of things, let’s kick it off with the mandatory last year in commits chart kindly provided by GitHub.

As you are probably aware by now, I am always hacking on multiple projects at any given time, and this year wasn’t any different, despite all that has transpired. While on the surface, sometimes it might appear that my side projects have nothing in common with each other, there’s always some overlap even if only in the sense of building a proof of concept of some piece that might fit into some of my other projects.

But, that’s enough of that! Let’s take a look at some of my highlights.

marmota

Earlier this year I ended up releasing marmota, an opinionated minimalist terminal emulator based on VTE.

It’s roughly 600 lines of C, and all configuration is done at compile time.

It has all the basic features that one might expect from a modern terminal emulator, including support for opening links, etc.

However, it doesn’t have built-in support for tabs, because you are better off with using a terminal multiplexer like tmux or screen to fulfill that need.

gustav.vim

Ended up writing another vim plugin, called gustav, which is a very simple GitHub Flavored Markdown compatible task/todo manager.

I also ended up making some enhancements to my previously released asynctotem.vim and fastopen.vim plugins.

dose2

Ported dose2 by mfx to WASM (web assembly) by leveraging emscripten.

Click here to see it in action.

DOSKit

DOSKit is a “small framework” that I started putting together to allow one to write small hobbyist DOS games without the need to install a 16 bit compiler toolchain.

How is that possible? It turns out that one can abuse gcc/clang and have it spit out 16-bit compatible machine code, for the most part. Obviously, it’s not without its problems, but hey it’s jolly good fun to play around with stuff like this.

In the screenshot above, you can see a small example embedding a TGA image with a palette and then blitting it into video memory.

And yes, it produces .COM files, which makes things more fun, because you have to limit yourself to 64K.

It’s not ready for prime-time just yet, but I might manage to get it over the finish line and release it sometime next year.

maksu

After using premake4 and then GeNIE for a number of years, I woke up one morning and said to myself, I am going to build my own “build automation tool”, but unlike premake, it will combine Lua + Ninja into a single statically linked self contained executable, that will enable single command builds across Linux, MacOS, Windows and FreeBSD.

Cross-compilation will not be an after thought, but rather a built-in as a core concept from the get go. Therefore, adding new toolchains (compilers, platforms) will not require hacks and monkey-patching.

You can just check-in the executable with the source code in your favorite source control system and bada-boom anyone who checks out the source code can build by running a single command. As with anything, there are and will be some caveats, but nothing too out of the ordinary.

I know that this might sound too good to be true, and maybe some of you might even say, why on earth do we need another build tool, but it’s going to happen, hopefully sometime early next year if the stars and planets align.

Calling it maksu for the time being, and as you probably guessed by now, it will be driven by a so called Maksufile.

GitHub Arctic Code Vault

The Arctic Code Vault was something totally unexpected and perhaps one of the very few truly cool things that have transpired this past year.

At least one of my personal projects, Floppy Bird, a boot-able flappy bird clone that I wrote in 16 bit assembly, got included in the vault. Even though this is one of my most popular projects of yesteryear’s, its inclusion in the vault still caught me off guard.

What a pleasant surprise.

However, I do want to bring up the fact that there was little to no information to be found about this whole thing and I noticed that I got the “Arctic Code Vault” badge totally by accident, when looking at my GitHub profile.

Also, the criteria based on which eligible projects/repositories were chosen to be included in the vault should have been communicated in a better way, rather than being buried and very hard to find.

GitHub Codespaces

A few months ago, I got early access to GitHub Codespaces.

What is codespaces? Simply put, it allows you to open any GitHub repository in a purely web based IDE. And this IDE is really just a VSCode that runs in your browser, rather than as a standalone executable via the means of electron.

It does look like a rather promising piece of technology. What makes it really convenient is that you get full shell access to the container that is spawned, which allows you to install any tools that you might need, expose ports, etc.

Another very convenient thing is that if you have a dotfiles repository, it will pull it in and setup your “terminal” and environment automatically.

I might write a more comprehensive standalone review/article about it next year.

Thoughts on Go

With the advent of Go 1.15.x, I thought that it’s about high-time I give Go a go, pun intended.

My initial impressions were good, but I still need to spend some more time with it and write something fairly non-trivial before jumping to or drawing any definitive conclusions.

There are still a couple of things that might rub you the wrong way, if you are person who wrote any non-trivial amount of C/C++ (myself included), but it does look like a promising language to complement C/C++, definitely not as a replacement.

https://mihail.co/2020/12/31/2020-retrospective/
2019 Retrospective
Show full content
2019 Retrospective

Here I am again, writing up this year’s retrospective on the last day of the year no less.

As per usual, let’s kick this off by sharing the mandatory “last year in commits” chart generated by GitHub.

Async Totem

Async Totem is a lean and mean async command runner for Vim 8 and later.

The reasons for me writing this plugin are two fold. First, I thought that it was about time for me to check out the async job support in Vim 8, and second I wanted to write a simple plugin leveraging it.

With that said, what better plugin to write than an async command runner?

Also, it’s worth noting that it’s 125 lines of vimscript (including the comments).

What was my general impression? It’s perfectly usable and just like all the extra features added on the top of Vim over the years, it does one thing only and it does it well, nothing else to comment, really!

Sobinka

I mentioned Sobinka, the general purpose 2D immediate mode game engine last year; while I still didn’t release it to the general public yet, did manage to figure out and even freeze some of the existing user (developer) facing API.

There’s still some discovery and experimentation to be done when it comes to nailing down the initial API that would be included for the v1.0.0 release, however, enough of it has been defined where I can start using it to sketch out some of the game ideas that I’ve been toying around with lately.

My plan is to wrap it up and release it sometime next year, but not before, I have at least one game prototype of my own up and running, which I can use to profile things in more or less real life scenarios.

I know that I said this last year, but I’ll attempt to blog about it in 2020, and go into some of the technical and architectural decisions I’ve taken while designing it. Nothing super duper revolutionary, but I do think that there are a couple of things lurking there that worth talking about.

13kjsgames

I made an attempt at coming up with something for the 13kjsgames competition (or game jam if you will), but sadly, I couldn’t finish my entry in time, and later I just put the project into the back burner for now.

Nevertheless, let me share a small gif showcasing the project.

.dotfiles

This year, I also simplified a lot of my dotfiles and colorschemes, as well as removed some Vim plugins that I wasn’t really getting all that much out of at the end of the day. I like to do this from time to time and try to get by with the bare minimum, without sacrificing customizability or flexibility.

As far as fonts are concerned, I am still rolling with the excellent IBM Plex Mono. If you are looking for a fresh and new monospaced font for the New Year, you should most definitely check it out!

End

Until next time, as server said, END OF LINE.

https://mihail.co/2019/12/31/2019-retrospective/
2018 Shakedown
Show full content
2018 Shakedown

Another glorious year is behind us and I’m here typing up a short summary of what I’ve been hacking on during the year.

All that being said, let me start by sharing the mandatory “last year in commits” chart generated by GitHub.

Sakura

I’ve been a long time terminator user, but as the years went by it got slower and slower, and buggier. Because of this, I ended up forking sakura and adding a couple of missing features like borderless window, etc.

Parsello

Parsello is a portable single file parser/lexer/tokenizer written in ANSI C (89).

Facts
  • minimalist API
  • small and lightweight (~ 450 LOC)
  • reasonably fast
  • zero copy and zero memory allocations
  • no dependencies (C std only)
  • compiles on your toaster (C89 and up)
  • doesn’t touch the “source input” string
  • single file
#define PRS_IMPLEMENTATION
#include "prs.h"

const char *s = "...";

...

prs_context_t ctx;
prs_token_t token;

prs_init(&ctx, s);

while(prs_parse(&ctx, &token))
  printf("'%.*s' of type %s on line %d\n",
         token->len, token->s, prs_token_type_to_str(token), token->line);

...

You can check it out over here.

O2D3M

This project started as a plugin for Tiled, but then I turned it into its own standalone command line utility and made it work with a Wavefront OBJ file, rather than a Tiled map.

What it does? Well, it takes a Wavefront OBJ file, parses out all the meshes and turns them into brushes and entities thus producing a DOOM3 map, which then can be compiled via dmap.

You can learn more about it over here.

Testa

Made some improvements to Testa, my portable single file “(unit) test framework” for C/C++.

#include "testa.h"

int sample_magic_numbers(void)
{
	int a = 42;

	TESTA_ASSERT_EQUAL(a, 13);

	return 0;
}

int sample_more_magic_numbers(void)
{
	int b = 42;

	TESTA_ASSERT_EQUAL(b, 42);

	return 0;
}

int sample_that_will_be_skipped(void)
{
	TESTA_SKIP();

	TESTA_ASSERT_NOT_EQUAL(13, 13);

	return 0;
}

int sample_is_not_null(void)
{
	int *p = NULL;

	TESTA_ASSERT(p != NULL);

	return 0;
}

int sample_pointer_equality(void)
{
	int *p = NULL;

	TESTA_ASSERT_EQUAL(p, NULL);

	return 0;
}

int sample_swap_numbers(void)
{
	int a = 13, b = 14, tmp;

	tmp = a;
	a = b;
	b = a;

	TESTA_ASSERT_EQUAL(tmp, 13);
	TESTA_ASSERT_EQUAL(a, 14);
	TESTA_ASSERT_EQUAL(b, 13);

	return 0;
}

TESTA_SUITE_BEGIN
	TESTA_GROUP_BEGIN("sample test group")
		TESTA_TEST(sample_magic_numbers)
		TESTA_TEST(sample_more_magic_numbers)
		TESTA_TEST(sample_that_will_be_skipped)
		TESTA_TEST(sample_is_not_null)
		TESTA_GROUP_BEGIN("sample sub test group")
			TESTA_TEST(sample_pointer_equality)
			TESTA_TEST(sample_swap_numbers)
		TESTA_GROUP_END
	TESTA_GROUP_END
TESTA_SUITE_END

And the results can be seen on the screenshot below.

You can check it out over here.

LSL

While LSL (Lua Shading Language) is not something I ended up releasing just yet, it was a fun little experiment composed of devising a pure Lua based DSL to output GLSL 1.2.

The idea would be that one has “different” backends that can output HLSL, GLSL, MLSL, etc from a common source language, without abusing a preprocessor or writing an actual parser.

This is very much inspired by some of the work of Petri Hakkinen.

Sobinka

Sobinka is a portable 2D game engine and framework written in C and it started as a small experimental sandbox that I’ve started building to test out a couple of the game ideas that I’ve been toying around with the past 2 years.

It’s made of a thin shell written in C that exposes relatively low level functionality via an immediate mode Lua API, which then can be used to build a game or even a regular desktop app.

Version 1.0.0 will support Linux, Windows, macOS (OSX) and HTML5 (web) via emscripten.

local tiled = import 'com.tiled'

local state = state or {}

function soa_on_init()
	soa_window_set_title('Binding')

	state.texture_id = soa_renderer_texture_load('textures/jerom')
	soa_renderer_set_texture(state.texture_id)

	state.map = tiled.find('maps/e1m1')
end

function soa_on_frame(dt)
	soa_renderer_clear(SOA_RENDERER_CLEAR_COLOR)

	tiled.draw(state.map)

    soa_renderer_draw(0, 0, 64, 64, 128, 128, 64, 64)

	soa_renderer_flush()

	if soa_input_mouse_clicked(SOA_MOUSE_BUTTON_LEFT) then
		soa_message_send('mouse_clicked', 'message mouse')
	end
end

function soa_on_message(id, message)
	print('on message', id, message)
end

function soa_on_quit()
	print('on quit')
end

I intend to post some more in-depth information about some of the technical aspects and decisions, in 2019; most likely after the first public release.

https://mihail.co/2018/12/31/2018-shakedown/
2017 Shakedown
Show full content
2017 Shakedown

Oh, it’s dat time of the year again. All in all, it was a good year, but aren’t they all good?

To put things into perspective, here are some charts generated by GitHub.

2017

2016

2015

A tad bit less contributions this year, compared to 2016, but as you can see on the chart it has been “more concentrated” (meaning, more dark green spots).

Fz2D

Fz2D received a batch of small updates, but it still didn’t graduate from alpha to beta, like I would have hoped.

With the advent of ES5 and ES6 being rolled out in all major browsers, I feel like the 5 minutes of fame of CoffeeScript expired and we can start writing slightly more structured things in pure JS, without resorting to all those 1998 style tricks.

All that being said, most likely I will “transpile” the code-base into modern JS with a tool like decaffeinate and then of course clean it by hand as necessary.

Feel free to take a peek at the documentation available over here.

Simon8

In other news, I ported my Chip 8 emulator (read: interpreter) to JavaScript via emscripten.

You can check it out over here.

Testa

I also released Testa which is a portable single file “(unit) test framework” for C/C++.

You can check it out over here.

ASMToy

This was something that I wanted to try out for quite some time now.

ASMToy is dosbox compiled to JavaScript running a modified Flat Assembler IDE for DOS.

The changes do not let you reboot or escape the confines of the IDE.

In effect, it lets you play with 16-bit assembly in DOS, right in your browser.

Doom Brush

This has been inspired by Joshua Skelly’s work, who is writing a small tool that can “transpile” a TMX Map (produced by the Tiled 2D map editor) into a Quake map.

In my case, I had two goals:

  1. “transpile” a Wavefront OBJ model file into brushes and entities
  2. write a Tiled plugin exporter plugin, that can export “object layers” as brushes and entities; with the brushes being constructed from the “polygon” objects defined within the editor itself (should be reasonably simple ..)

Managed to achieve the first goal and I am quite happy with the library I wrote, but it still needs a little bit of clean-up, because some the of API is a bit confusing at the moment.

As far as the second goal is concerned, I managed to “sketch” out the skeleton of the exporter plugin, but didn’t get to implement any of the actual functionality.

I’ll release the source code most likely sometime next year.

Nixel

Yet again, my primary focus has been Nixel which is the codename of the game engine and framework I’ve been hacking on for ages.

This year, I managed to figure out a number of technical conundrums and investigated which “GUI framework” to use for the tools. In the end, I just decided to roll my own “Immediate Mode GUI” (IMGUI), because everything had too many dependencies or some weird and intricate layout system.

For some time, I considered that perhaps Electron could be the answer, because now that we have flexbox, one doesn’t have to resort to “floating elements” and “tables” to make reasonably complicated layouts.

But in the end, it felt like pulling in way too many dependencies and that is something that I am paying enormous amounts of attention to.

One of the primary problems of both closed and open source projects are dependencies.

If the dependencies are hard to install or hard build and not self-contained enough to be bundled with the project, then it becomes a nightmare to build and maintain the project. In turn this will automatically increase the barrier of entry for new contributors, as well as hinder porting and general maintenance over time.

I also decided to roll with JS as a scripting language, relying on the excellent Duktape, instead of Lua. We’ll see how this will pan out in the long run, but so far it appears that it will be “fast” enough and the GC can be kept under control.

You have to realize that, we are talking about a very tight event loop, where every millisecond is precious and one wants to waste as few as possible.

Next year, I’ll try to delve into these things in a little bit more detail, so you can expect more content from me, rather than the average of 1-2 posts per year, which is nothing less than appalling if you ask me.

Nintendo Switch

Like millions of people around this piece of this of a rock floating in space, I got a Nintendo Switch, shortly after its release, together with the Wolf Link Amiibo.

Game Medium Hours Played Notes The Legend of Zelda: BOTW cartridge 200+ n/a Kingdom digital 30+ n/a Skyrim digital 10+ n/a Snake Pass digital 2 or less n/a Mario Kart 8 Deluxe digital 1 or less n/a Oceanhorn digital 1 or less n/a Shephy digital 1 or less n/a Morphite digital 1 or less n/a Little Inferno digital 1 or less completed on PC World of Goo digital 1 or less completed on PC The Flame in The Flood digital 1 or less n/a

By the way, NO, I didn’t lick the cartridge.

All I can say is that Nintendo did it again. If only Sony would have invested slightly more in the PS Vita (which I also own), I think that this could have been more of a battle, rather than just stealing the show without any competition.

The dual function of the Switch is the prime thing that makes it apart and contributed to its resounding success.

After all, we all know that the Switch was been DOOMed from the get go, right?

End

And here we are. Before I wrap this up though, my game of the year has been The Legend of Zelda: BOTW, without any doubts. I mean, it’s quite self evident given the number of hours I sank into it.

Oh and also, after many years of using Meslo LG Mono as my primary font, a few months ago I made the switch to IBM Plex Mono. So far so good, but it still remains to be seen if I’ll revert, because I am not totally convinced just yet.

https://mihail.co/2017/12/31/2017-shakedown/
2016 Shakedown
Show full content
2016 Shakedown

Another year is behind us and I am here once again posting a summary of what I’ve been hacking on in my spare time, which translates to evenings and weekends.

This year was more of a year of ideas when it comes to my personal projects. I did not release a great many things, but fiddled with various ideas and crystallized the road ahead.

To put things into perspective, here are some charts generated by GitHub.

2016 vs 2015

Menhir

Menhir is a simple puzzle game, the player’s sole purpose is to try to clear the board by utilizing empty spots.

This is modeled after a “board game”, I really enjoyed in my childhood.

Special thanks go out to Kenney “Asset” Jesus for the sprites.

If you want to try it out, you can do so by clicking here.

Fz2D

Fz2D received a couple of more updates, and slowly but surely getting closer to graduating from alpha into beta.

I’d like to add / wrap-up the following things next year:

  • Fz2D.Camera (scrolling, etc)
  • Built-in asset pipeline (texture and font atlas generation)
  • Improved (painless) handling of dependencies

Feel free to take a peek at the documentation available over here.

nyan.c

This was a warm-up for IOCCC, but then it turned out that there was no competition held this year. Oh well ….

#include <stdio.h>
#include <unistd.h>

int main(){const char *O_o[]=/*msz*/{
" ,------,\n"  "v|   /\\__/\\ \n"
" |__( ^ w ^) \n" "     \"  \"\n",
	     " ,------,\n"
	     "~|    /\\__/\\\n"
	     " |___( ^ w ^) \n"
	     "     \"  \"\n",
" ,------,\n" "^|    /\\__/\\\n"
" |___( ^ w ^) \n"	"     \"  \"\n" };
int i=1;for(;;){printf("%s\x1b[4A",
O_o[(i=(i+1)%3)]);usleep(200000);}}
ZeeCraft

Added an HD mode to my ZeeCraft project.

Special thanks, once again go out to Kenney “Asset” Jesus for the textures.

fastopen.vim

I made a few updates to fastopen.vim. A quick “file opener” for vim using “dmenu”.

JSIP

JSIP can pack a piece of JS into a plain PNG image. Does not rely on any custom chunks or other exploits.

input: helloworld.js
alert('hello world');
output: helloworld.html
<html>
  <head>
    <title>Helloworld</title>
  </head>
  <body>
    <img src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAQAAAAABCAAAAAAUMi+rAAAAIUlEQVR42mNI
zEktKtFQz0jNyclXKM8vyklR17RWGDkAAJWCJK98ao+pAAAAAElFTkSuQmCC
"
         style="visibility: hidden; position: absolute; top: 0; left: 0;"
         onload="this.remove(); var c=document.createElement('canvas').getContext('2d'); c.canvas.width=this.width; c.canvas.height=this.height; c.drawImage(this,0,0); var p=c.getImageData(0,0,this.width,this.height).data; var s='';for(var i=0;i<p.length;i+=4)s+=String.fromCharCode(p[i]); (1,eval)(s);" onerror="this.remove();" />
  </body>
</html>

Even with the indirect eval, the resulting code is slower than the same code included and executed directly; but it was a fun thing to experiment with, nonetheless.

To view a non-interactive visual demo produced by JSIP, click here.

Noty

Noty is a small note taking application, I written in pure bash script. It’s roughly 200 lines long, including comments and stores notes in GFM.

It is possible to store all notes in a user specified location by exporting the NOTY_DB variable in your .bashrc like so:

export NOTY_DB=$HOME/Dropbox/.noty

The command line interface is really simple.

Noty v1.0.0

usage: noty command [arg] [note]

commands:
  c - create a new note
  l - list notes
  m - mark a note
  u - unmark a note

A little example is in order to demonstrate how lean and mean it is to use.

 ~ $ noty c 'hello world'
Initialized 'testdb' ...
Initialized '2016-12-30' ...
Added note 'hello world' ...
 ~ $ noty l
2016-12-30
----------
1. [ ] hello world
 ~ $ noty c 'make cake'
Added note 'make cake' ...
 ~ $ noty m 1
Marked note '1' ...
 ~ $ noty l
2016-12-30
----------
1. [x] hello world
2. [ ] make cake
#!/bin/bash

#
# MIT LICENSE
#
# Copyright (c) 2016, Mihail Szabolcs
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the 'Software'), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in all
# copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
# SOFTWARE.
#

NOTY_VERSION='1.0.0'

if [[ -z "$NOTY_DB" ]]; then
	NOTY_DB="$HOME/.noty"
fi

function get_current_date()
{
	date +'%Y-%m-%d'
}

function preg_replace()
{
	sed -i -e "s/$1/$2/" $3
}

function preg_remove()
{
	sed -i -e "/$1/d" $2
}

function db_init()
{
	if [[ ! -f $NOTY_DB ]]; then
		echo -e "Noty v$NOTY_VERSION\n===========" >> $NOTY_DB
		echo -e "You have a total of 0 notes." >> $NOTY_DB
		echo "Initialized '$NOTY_DB' ..."
	fi
}

function db_get_id()
{
	local OLD_ID=$(grep -m 1 -P '^You have a total of \d+ notes.$' $NOTY_DB | cut -d ' ' -f 6)
	local NEW_ID=$((OLD_ID + 1))

	preg_replace "^You have a total of $OLD_ID notes.$" "You have a total of $NEW_ID notes." $NOTY_DB

	echo $NEW_ID
}

function db_write_note()
{
	db_init

	if [[ -z $(grep "^$(get_current_date)" $NOTY_DB) ]]; then
		echo -e "\n$(get_current_date)\n----------" >> $NOTY_DB
		echo "Initialized '$(get_current_date)' ..."
	fi

	echo "$(db_get_id). [ ] $@" >> $NOTY_DB
	echo "Added note '$@' ..."
}

function db_delete_note()
{
	preg_remove "^$1. \[ \]" $NOTY_DB
	echo "Deleted note '$1' ..."
}

function db_get_notes()
{
	if [[ "$1" == "all" ]]; then
		cat ${NOTY_DB}
	else
		local LINE=$(grep -m 1 -n -P "^$1" $NOTY_DB | cut -d ':' -f 1)
		if [[ -n "$LINE" ]] ; then
			tail -n +$LINE $NOTY_DB
		fi
	fi
}

function db_mark_note()
{
	preg_replace "^$1. \[ \]" "$1. \[x\]" $NOTY_DB
	echo "Marked note '$1' ..."
}

function db_unmark_note()
{
	preg_replace "^$1. \[x\]" "$1. \[ \]" $NOTY_DB
	echo "Un-Marked note '$1' ..."
}

function command_c()
{
	if [[ -z "$1" ]]; then
		echo "You must provide a valid note."
		exit -1
	else
		db_write_note "$@"
	fi
}

function command_d()
{
	if [[ -z "$1" ]]; then
		echo "You must provide a valid note id"
		exit -1
	else
		db_delete_note "$1"
	fi
}

function command_l()
{
	if [[ -z "$1" ]]; then
		db_get_notes $(get_current_date)
	else
		db_get_notes "$1"
	fi
}

function command_m()
{
	if [[ -z "$1" ]]; then
		echo "You must provide a valid note id"
		exit -1
	else
		db_mark_note "$1"
	fi
}

function command_u()
{
	if [[ -z "$1" ]]; then
		echo "You must provide a valid note id"
		exit -1
	else
		db_unmark_note "$1"
	fi
}

function help()
{
	echo "Noty v$NOTY_VERSION"
	echo -e "\nusage: $(basename $0) command [arg] [note]"
	echo -e "\ncommands:"
	echo -e "\tc - create a new note"
	echo -e "\tl - list notes"
	echo -e "\tm - mark a note"
	echo -e "\tu - unmark a note"
	exit -1
}

function main()
{
	local CMD="$1"
	shift

	local FUNCTION_TYPE=$(type -t "command_${CMD}")

	if [ "$FUNCTION_TYPE" == "function" ]; then
		command_${CMD} "$@"
	else
		help
	fi
}

main "$@"
SteamSpy

If you like to spy on Steam games a lot like I do, then you might find the bookmarklet or the TamperMonkey script below very very useful.

Bookmarklet

Open SteamSpy on any Steam store page with a single click.

javascript:window.open(window.location.href.replace('store.steampowered', 'steamspy'))
TamperMonkey Script

This script adds a SteamSpy button to every Steam store page.

// ==UserScript==
// @name         SteamSpy
// @namespace    http://mihail.co
// @version      0.1
// @description  Adds a Steam Spy button to every Steam store page.
// @author       Mihail Szabolcs
// @match        http://store.steampowered.com/app/*
// @grant        none
// ==/UserScript==
(function() {
    'use strict';
    var el = document.querySelector('.apphub_OtherSiteInfo');
    if(!el) return;

    var a = el.querySelector('a');
    if(!a) return;

    var newa = a.cloneNode(true);
    newa.href = 'javascript: window.open("https://steamspy.com' + a.href.match(/\/app\/([0-9]+)/)[0] + '");';

    var text = newa.querySelector('span');
    if(!text) return;

    text.innerHTML = 'Steam Spy';

    el.appendChild(newa);
})();
Hamunaputra

Hamunaputra started as a 32KB room-based roguelite for DOS, written in plain 16-bit ASM, with everything being packed into a single .COM executable. No external files, or dependencies of any kind.

Never finished it, and currently sits in the back burner.

Nexeh & Nixel

This has been my primary focus this year, and will continue to be next year. Like I mentioned before, I would like to build and old-school first person shooter, retaining as many original elements as possible, while at the same time throwing a couple of new twists into the mix for good measure.

My definition of old-school includes (but not limited to), games like:

  • DOOM
  • Hexen and Heretic
  • Duke Nukem 3D
  • Painkiller
  • Shadow Warrior

I want to go for the 2.5D look, with 3D world geometry and 2D sprite based (billboard) entities. Haven’t settled for a general look and feel yet, because I am still working on some of the core tech.

… with some sprites ripped from Hexen …

… and compiled with emscripten

I managed to figure out most of the architectural questions; will try to blog about some of the technical stuff in the weeks to come. Stay tuned.

Like I promised, it’s all going to be open source when it comes to the code and tech. When am I going to open source it? Well, that remains to be seen, but most likely only after I have something more or less playable in my hands.

Greed

Stumbled upon In Pursuit of Greed and decided to modernize and port it, keeping the original software renderer intact, while allowing for higher resolutions, both upscaled (from 320x200) and native.

Here you can see a screenshot of the game running at whopping 800x600 resolution.

This is still very much work in progress, and I work on it every once in a while, but we’ll get there sooner or later.

Writings

There is plenty of literature when it comes to game development, but none of it is focusing on the actual so called practical side of things. There are bits scattered here and there, but nothing exhaustive or conclusive.

Most of the books, just rehash and regurgitate the very same things, perhaps updated or modernized in places, but still presented in rather outdated formats.

To change the status quo, I started sketching out three books, Software Rendering for the practical game developer, Game programming for the practical game developer and a companion guide entitled, Mathematics for the practical game developer.

I want to focus purely on the practical side of things, avoid platitudes and urban myths, however, when any of these will turn into actual paper that one can hold in their hands, one again, it remains to be seen …

On a different note, there’s still considerable interest in my FloppyBird project, which means that it’s time for me to write a Making of Floppy Bird, sometime next year …

Pyramid Hologram

Before I even get started, yes, yes, yes, it’s not a real hologram, but it’s still 10x more interesting than the idiotic thing we call VR. Who on Earth wants to stick half a kilogram of equipment on their head? Apparently quite a few. Good luck with destroying your eyesight faster than a good old CRT would.

Now that we have that settled, let me dive into the details.

The so called “DYI holograms” have been circulating on the Internets for quite some time, but all efforts seem to be focused on displaying animations, instead of real-time applications, like video games.

Basically, you build a pyramid and then you use a tablet or another device to project on it by placing it on the top, you can see this in action in the video above.

(Source: ~mhsung)

In order to achieve the effect, one has to prepare a special video, which you can see below.

What’s really going on here? The answer is really simple. It’s the same thing rendered from 4 different angles, which is then reflected on the 4 sides of the pyramid, giving the illusion of a volumetric hologram.

Do you see where this is going? When it comes to games, all one has to do is render in real time the visible scene from 4 different angles, by rotating the so called camera.

Isn’t that expensive, to render almost the same thing 4 times? Well, it really depends on a couple of things like: complexity of the scene, output resolution and overall graphical fidelity.

I want to build a prototype with something relatively simple, like Tux Racer. Take a look at the small sketch I made to illustrate the concept, in order to put things into perspective.

In many respects this is similar to split screen local couch co-op, where the screen is split into 2 to 4 individual viewports giving each player the ability to roam around freely.

Left 4 Dead 2

Super Mario Kart

Imagine sitting at a table and each player seeing the very same world from a different view. Wouldn’t that be super duper cool? I’ll let you decide …

Rover

Even though Elon Musk is very very optimistic when it comes to colonizing Mars, and yes he is definitely going to democratize the rocket business, but we are very very far away from man setting foot on Mars.

NASA got their rover moving around, snapping a few photos here and there, but every time something interesting pops-up on one of those photos, they always seem to be very eager to discredit it or just blatantly ignore it.

I fully understand, you can’t just move around a few billion dollars worth of machinery, willy nilly, as you please. I get that 100%, but when there’s considerable doubt when it comes to certain geological features that appear to resemble man-made structures or statues, the silence and the same old same charade becomes quite annoying.

Nobody is saying that every rock formation that looks suspicious is a statue or some relic left there by some ancient civilization, but all it would take is to move closer to one instance and take a closer photo. That’s all it would take, and then all the crazy conspiracy theorists would just shut up and crawl back into the caves they emerged from.

I’ve been thinking about this, what if, we could create an inexpesive machine which could roam around freely, with its sole purpose being taking high resolution close-up photographs of certain points of interests.

A few details about the sketch above:

  • in terms of scale, it should be the size of a regular sitting ball
  • the outer shell rotates around the inner shell
  • the inner shell is always axis aligned, regardless of the outer shell
  • 3 solar panels, which means that two of them will receive sunlight regardless of the rotation of the outer shell
  • the inner shell contains a black-box with all the equipment (camera, comm-link, a mini PC, etc)
  • the outer shell should absorb any shock (i.e when landing)

I am pretty sure that there are many things that I didn’t think of, or things that are just plain wrong or impossible with such a design, but the general idea would be to build a dozen of these and then let them loose on the surface or Mars. Each of these could visit a place of interest and send back high resolution photographs.

Since it wouldn’t contain any expensive scientific equipment the building costs should be minimal, compared the rovers that have been deployed in the past.

SketchFab

A couple of my favorite sketchfabs.

End

Phew, if you managed reach THE END, you are as brave as you are foolish. All in all, it has been an interesting year, like I mentioned above, my main focus for 2017 is going to be my old school first person shooter project. It would be nice to reach some sort of a playable tech-demo state, sooner rather than later.

Since I am not a console gamer, my new years resolutions cannot be as simple as 720p, I hope that you can understand that. (PC MASTER RACE FOR LIFE REPRESENT)

Oh, I almost forgot, mostly because I’ve been rambling for so long, my game of the year, IS most definitely, without a shadow of a doubt, silence and drum rolls, Firewatch.

To view all the photos I took in-game, click here.

Jane Ng and the rest of the team did an amazing job when it comes to crafting the world of Firewatch.

Until next time, as server said, END OF LINE.

https://mihail.co/2016/12/31/2016-shakedown/
2015 Shakedown
Show full content
2015 Shakedown

Another year passed and I didn’t honor my promise about posting more on this blog.

Next year, I will try to post at least once every month and I feel like there will be plenty of things to talk about :)

Before we proceed any further, one of the first (NY) changes you’ll notice, is that this blog has been moved over to mihail.co. It’s just a better and less confusing domain with an easily pronounceable name. Everybody heard about Mihail Gorbachev after all.

If you are subscribed to the blog via RSS, the old “feed” should continue to work as expected and should require no changes, but those of you adventurous souls can subscribe to the new RSS.

On a totally different note, I did release quite a few interesting projects this year, but as always, not nearly enough to satisfy my ever growing hunger.

Without any further ado, down below, you can read about what kept my gray cells busy during the year.

Fz2D

Fz2D is a 2D game engine and framework I released last year.

This year it received quite a few updates and got to version 0.0.2, which means that it’s still alpha but at the same time it reached a certain level of maturity, so to speak.

Deermas, Bricks of Babel and Snakr are all powered by Fz2D.

Deermas, for instance, was thrown together in one and a half days. Obviously I did not create the actual sprites and music, got them from opengameart.org, but still, it felt quite nice, just be able to fit the parts together and get a basic game up and running in practically no time at all.

The API is still not set in stone, but all in all it’s quite consistent and doesn’t try to do more than it is absolutely necessary.

Feel free to take a peek at the documentation available over here.

Deermas

A mini adventure game for Maral.

You can find out more about it over here. If you just want to play it, you can do so by clicking here.

Bricks of Babel

Bricks of Babel is a physics based tower building game. Initially built for Ludum Dare.

You can find out more about it over here. If you just want to play it, you can do so by clicking here.

3

Minimalist Breakout clone.

If you want to play it, you can do so by clicking here.

Simon8

Simon8 is a Chip 8 disassembler and emulator written in C.

This is something that I wanted to do for so long, but never really took the time to do it. Here it is, full blown suite of tools composed of a disassembler and an emulator for the Chip 8.

The tools follow the *nix principles and support pipes, as well as file arguments.

build/disasm roms/MAZE
cat roms/MAZE | build/disasm
0x200: LD    I, 0x21E
0x202: RND  V2, 0x01
0x204: SE   V2, 0x01
0x206: LD    I, 0x21A
0x208: DRW  V0, V1, 0x04
0x20A: ADD  V0, 0x04
0x20C: SE   V0, 0x40
0x20E: JP   0x200
0x210: LD   V0, 0x00
0x212: ADD  V1, 0x04
0x214: SE   V1, 0x20
0x216: JP   0x200
0x218: JP   0x218
0x21A: LD   V0, V4
0x21C: CALL 0x010
0x21E: CALL 0x040
0x220: LD   V0, V1

You can find out more about it over here.

Snakr

Snakr is a “Snake clone” with a twist. Initially built for Ludum Dare.

You can find out more about it over here. If you just want to play it, you can do so by clicking here.

ZeeCraft

ZeeCraft is a mini Minecraft clone in 588 lines of C.

This was one of those “because I can” projects more than anything else and indeed managed to do it in mere 588 lines.

Tech Details

The actual world size is 16x16x16 units (cubes) and there’s no frustum culling, meaning that the whole world is drawn each time, but at this scale it really doesn’t matter. It does have back-face culling though.

The various block types are compiled display lists.

To keep the amount of code within limits, it requires OpenGL 1.4 and relies on fixed function pipeline only.

SDL is used for window management and input handling.

As an added bonus, the world is automatically persisted and loaded between runs.

You can find out more about it over here.

Floppy Bird

Floppy Bird is one of my most popular projects and this year it got a minor update.

It is now possible to compile it into a DOS compatible .COM executable, which then can run in any DOS like environment.

You can find out more about it over here.

2dimagefilter

I forked 2dimagefilter and removed all exotic GUI crap, basically everything that wasn’t implemented by Mono.

The command line interface is now usable on any platform where Mono is supported.

ImageResizer /load in.png /resize auto "XBR 4x(2,thresholds=0,vbounds=wrap)" /save out.png

You can find out more about it over here.

fastopen.vim

fastopen.vim is a quick “file opener” using “dmenu”. It is my very first Vim plugin.

You can find out more about it over here.

Nexeh

Around a year ago, I had the idea of building an old-school first person shooter from scratch. Instead of jumping all in, I decided to build a tech-demo / prototype first, release it, iterate on it, and then potentially, use the same tech and create the full blown game I had in mind.

This is where Nexeh comes into play. Initially, I wanted to build it exclusively while streaming on Twitch which sort of didn’t work out, but I do want rectify that next year and stream some game development more often.

This also gave me the chance to finally sit down and play around with emcripten, as you can see it in the screenshot above.

I ended up creating a nice custom shell which looks pretty decent, I have to say.

What do I mean, when I say old school first person shooter? Well, first of all the “world” is made of true 3D geomery, but everything else consists of 2D sprites, or billboards if you will and they always face in the player’s view direction.

In addition to all this, you get attacked and potentially can get yourself killed within the first minute of the game. No hand holding, no regenerating health or manna.

Obviously, there’s more to it than what I just said above, but I’ll try to talk about specifics next year. Everything I described here should give you a sneak peek into what’s about to come.

One thing that I really want to make clear, the art direction isn’t going to be pixel artsy or cartoony. At any rate, it’s too early to talk about specifics regarding this matter, but I just wanted to get that out there into the wilderness.

Also, for obvious reasons, Nexeh is going to have nothing in common in terms of story or setting (locations, characters, etc.), with the actual final game I wanna build. I choose to do this, in order to avoid false expectations and confusion.

My hopes are to procrastinate less and actually have something playable, sometime in Q1 of next year. Please keep in mind, this is a project that I work on in my spare time, it is not my full time job (ATM), therefore, a great many factors will influence the amount of progress that I can achieve.

Once there’s something playable, I’ll reach out to some of my artsy friends and we’ll figure out the actual art direction, plus the general look and feel.

In the long term, the tech that I build for Nexeh is going to be extracted and then turned into a standalone general purpose “open source” game and engine framework, called Nixel, but more about that another time.

RU FLAG

In the wake up all the madness that went down this year, I put together this little page where one can drag a photo and the photo will get the colors of the Russian flag applied to it.

This is similar to the feature that exists within the realms of Facebook.

You can find out more about it over here. If you just want to play it, you can do so by clicking here.

RU

I also started learning the Russian Alphabet more seriously, but I still find it absolutely mind wrecking and therefore I did not make as much progress with it as I would have liked to.

p = r, c = s, …. gaaah, my gray cells are confused!

End

Well, pretty much that’s it for this year. Tried to cover everything, important.

I am not really a new years resolutions kind of person and therefore I do not have a list, however, here are some things that I’d like to do, given that everything goes right in the coming year, without being complete, by any stretch of the imagination.

  • post more often on this blog
  • stream more game development
  • successfully master the Russian Alphabet
  • finish and release Nexeh (hopefully, this won’t take an entire year, sigh)
  • new workstation
  • Russian wife
  • *lay the foundations* of a **grassroots** communist political party

Until next time, as server said, END OF LINE.

https://mihail.co/2015/12/31/2015-shakedown/
2014 Shakedown
Show full content
2014 Shakedown

It’s been a quite long time since I discussed anything on this blog.

One could say that I neglected it, which is not an entirely accurate claim, but it’s not very far from the truth.

I do have a bunch of ideas to write posts about, but most of them would conflict with my basic rule of three: No sex, politics or religion on the blog.

Anyways, all is well, 2014 has been a year where I released quite a few open source projects.

I will talk about some of my work-in-progress-not-yet-open-source projects in a series of separate posts starting next year.

Please note, the projects listed below are not in chronological order of their release date.

Oh, and by the way, I didn’t get married.

Floppy Bird

Floppy Bird is a clone of the infamous Flappy Bird written in 16 bit (x86) assembly.

You can find out more about it over here.

Fz2D

Fz2D is a 2D game engine and framework.

You can find out more about it over here.

NVX

NVX is an image “voxalizer”. It will take a 2D image and create a 3D model.

You can find out more about it over here.

Nine Dots Dungeon

Nine Dots Dungeon was my entry for Ludum Dare 31.

You can find out more about it over here.

Sniknej

Sniknej is a standalone front-end for Jenkins.

You can find out more about it over here.

Jsonoid

A simple serverless NoSQL (JSON) document storage system.

require 'jsonoid'

Jsonoid.configure do |config|
  config.db = File.join(File.dirname(__FILE__), 'db')
end

class Post
  include Jsonoid::Document
  include Jsonoid::Timestamp

  before_save :add_byline

  field :title
  field :description
  field :author, :type => String
  field :score, :type => Integer

  protected

  def add_byline
    self.description += "\n#{self.author}" unless self.author.nil?
  end
end

post = Post.new(:title => 'Hello World')
post.description = 'The quick brown fox jumps over the lazy dog.'
post.author = 'Fox'
post.score = 10
post.save
post = Post.find('2cfe7b2e885f225746264b3c6c0beb57')
post.destroy unless post.nil?

You can find out more about it over here.

Cowmos

Cowmos is a small birthro for Valeria. You can watch it over here.

You can find out more about it over here.

nanoenum.rb

A small enum implementation for Ruby.

class Model
  include Mongoid::Document
  include NanoEnum

  TYPE = enum :apple => 15, :orange => 16

  field :type, :type => TYPE, :default => TYPE::ORANGE
  # field :type, :type => Integer, :default => TYPE::ORANGE
  validates_inclusion_of :type, :in => TYPE
end

You can find out more about it over here.

Gimp Font Atlas Creator

A small Script FU plugin for GIMP that creates font atlases. Ideal for mono-spaced fonts.

You can find out more about it over here.

vm.rb

A small “experimental” (x86 like) virtual machine written in Ruby.

;
; Prints first N from the Fibonacci sequence.
;
read_n:
  mov dx, 78
  prc dx

  mov dx, 58
  prc dx

  rdr dx
  cmp dx, 0
  je read_n

mov cx, 0
mov ax, 0
mov bx, 1

loop:
  prr bx

  push bx
  add bx, ax
  pop ax

  add cx, 1
  cmp cx, dx
  jne loop
ruby vm.rb fibo.asm

You can find out more about it over here.

pong.rb

Quick and dirty Pong clone written in Ruby that runs in your terminal. Requires ncurses.

You can find out more about it over here.

scrsat

Screenshot Saturday Twitter Scrapper.

You can find out more about it over here.

mkatlas

BASH script to build a texture atlas for games. Requires ImageMagick.

While not entirely written by me, I did some improvements to it.

You can find out more about it over here.

https://mihail.co/2014/12/31/2014-shakedown/