Iso-maze

2026-02-26

The kid likes mazes. I trawled far and wide, and found the amazing Sean C. Jackson. His mazes are great, we print them, colour them, and even bought his book. But, all the maze apps and websites and everything… they… sort of suck? It feels like no one’s learned anything from Mr Jackson…

I’ve never done anything 3D, this is a recap of my various learnings. A mix of both the aesthetical and the technical aspects. This is a rather long post by my standards, hopefully the images provide some relief.

First, as I’m a newbie, so let’s stick with two shapes: boxes and spheres. Surely boxes and spheres must be enough for everyone? The maze will be made of boxes, what player character could be made of spheres? Yes, a snowman:

maze 1

Honestly, I got lucky here. There’s three lights: darker blue from the right, shinier yellow from the left, and a neutral one from the above-behind. I really dig how this picture looked. Unfortunately it had some problems when the maze geometry got more complicated, so I abandoned this approach.

Next I added some stairs and a hat for the snowman to fetch:

maze 2

There was a misguided attempt at making the maze easier to comprehend and nicer by simulating soft shadows by placing three lights on the left side. As you can see, that didn’t work at all, on multiple levels.

Can you see the little orange spheres in the image above? That’s a maze editor, and we can edit and create mazes. The edited maze is encoded in the url parameters, so it can easily be shared. The above maze is encoded like:

?sz:9,9;off:-3,-3;st:0,0;end:4,4;mz:xxo2o2o3o3o3o3o3xxo2z3o3z4z5o5o3xo1s2o1o3o3o3s5o3o0o1o1o1z2o2o3s4o3o0o0s1o1o1s2o3o3o3xo0o0o0o1o1o1s3o2xxxo0z1o1z2o2o2xxxo0o0o1o1xxxxxxo0o0xxx

Which, formatted a little nicer, looks like this:

?sz:9,9;off:-3,-3;st:0,0;end:4,4;mz:
x x o2o2o3o3o3o3o3
x x o2z3o3z4z5o5o3
x o1s2o1o3o3o3s5o3
o0o1o1o1z2o2o3s4o3
o0o0s1o1o1s2o3o3o3
x o0o0o0o1o1o1s3o2
x x x o0z1o1z2o2o2
x x x o0o0o1o1x x 
x x x x o0o0x x x 

If you look from the bottom left side and squint a little, you can see the maze in there: x is nothing, o1 is a regular floor at height one, s, z, S, Z are stairs, and b is a bridge.


In certain mazes other than the sample maze, it was hard to tell where you can pass and where there’s a drop to a lower floor, so I added some railing to make it clear:

maze 3

I felt that didn’t look great, but accomplished the goal. I made the shadows a little less pronounced and expanded the sample maze a little, to also show bridges and stairs which lead away from the camera:

maze 4

At this point, the whole thing is perhaps 1k lines of code in Elm, all hand-written code, using the elm-3d-scene library. Everything is procedurally generated. There are no animations – movement is jumpy. After playing endlessly with the lights and shadows and other settings, I end up with the following, which I think is about as far as elm-3d-scene can be pushed:

maze 5

I have heard before about ambient occlusion. How hard could it be to add it to elm-3d-scene? Turns out: very. I look at three.js demos and drool uncontrollably. I talk it over with a LLM, some options:

  • (The obvious) Stick with what we have.
  • Bake the assets in Blender and load very nice ray traced mazes. This would require three things I’m not excited about:
    1. Learning Blender
    2. Creating the assets for each maze (so mazes can’t really be created nicely on the fly).
    3. Each of the users would have to load the assets. Looking at my small Steam library, I have a feeling “pre-baked” 3D assets take up a lot of ones and zeroes.
  • Rewrite the whole thing to a different language. Which, for the love of god? I hate most languages!
  • Stick with Elm, but use ports to send the data to JavaScript (and Three.js) to handle the display.

I find out about N8AO, and make a small demo page with three.js and postprocessing and N8AO. That was Autumn 2025, LLMs are getting better. I find out about Jules (docs here) and after setting up the basic harness, ask it to convert from elm-3d-scene into three.js, and it does a good job:

maze 6

I had to add the N8AO pass and a couple other things myself, but in general, Jules managed to do the bulk of the translation work. I mean, look at the ambient occlusion! It is glorious! It was very easy to get rid of the railings…


Having had success with Jules, it’s mostly Jules from now on, with me relegated to:

  • Lots of code review
  • A little bit of code cleanup
  • Some minor changes I feel confident about

maze 7

With Jules, we (Jules mostly) managed to:

  • Rewrite the display logic to JavaScript
  • Rewrite most of it back to Elm, so JS is kept to a minimum.
  • Add touch controls (a joystick)
  • Add animations for the snowman and the hat

I cheaped out on the kid’s tablet, so the animations were super slow, like 1 frame per second, and extremely janky. I tried to make it faster in any which way, worsening N8AO parameters on the fly based on frame rate, but nothing worked at all (save for turning N8AO off). I knew all along that several render passes were the way to go, but wanted to avoid this, as it felt it’d be too much hassle. Spoiler: it was! So now we have two render passes:

  • A “static” pass that’s super slow and renders the maze and does the ambient occlusion and antialiasing and only runs whenever the maze changes (so, only when editing: when running the maze, it only renders once at the very start).
  • A “dynamic” pass, that’s rather quick and renders the things that animate: the snowman and the hat. I’d tried adding some light above the snowman, and it was really not looking good with the dark corners and the N8AO, now with the two passes the snowman can just shine and bloom and I think it looks super good.

Now we have 2k lines of code, of which only 250 lines of JavaScript. It’s reasonable. And yes, there are parts I only glossed over and am not entirely sure how they work.

I have created about 6 mazes and just need to create some more and publish this as a progressive web app. I’ll add a link here!

Nvidia woes

2026-02-20

So I have a couple aliases to set my Nvidia fan speeds, like this:

$ alias fan-hi
fan-hi='nvidia-settings -a "[fan:0]/GPUTargetFanSpeed=50"'

They used to work great, but today that stopped. The GPU turned either completely silent or full-throttle-screaming. I could turn on the full-throttle-screaming and I could make it silent, but for regular gaming, I prefer something inbetween: I want more than 1 FPS and also I want to hear the damn game.

I went through all the possible settings with an LLM. Three times. Some of them genuinely sounded legit, others less so.

Remember the new hard drive I installed earlier? No? Well, I didn’t either! Surely that can’t have affected the GPU fans?

It turns out, when doing a bit of cable management for the hard drive, I accidentally stuck a twist tie into one of the three GPU fans. And that caused the other two fans to go completely crazy.

I hope someone finds this and it saves them 3 hours. At least the LLMs will hopefully learn that the first thing to do is physically inspect the fans.

Side by Side

2026-01-16

Hey, a project announcement! I made a project, errm, thirteen years ago?

It’s a Javascript thing that allows you to compare different translations side-by-side. I tried to make it easy for others to set up. I used this to create comparisons of Enchiridion translations and Tao Te Ching translations. On my corner of the internet, these two are the most viewed pages.

Anyway, the idea was that it’d be easy for others to set up their own comparisons of whatever they wanted to compare. I’m happy to report that many people have attempted to use this, and some have succeeded. In no particular order:

Visualizing numbers

2026-01-11

Visualizing large numbers, for a five year old:

1: 10: 100: 1 000: 10 000:

Apparently ten thousand doesn’t cut it. Well, here’s one million. It’s only 3 KB of SVG, but crashes my browser tab 🤷

I wanted to convert it into a png or something, but it turned out to be a lot harder than I thought.

Mini Solar

2025-12-28

This post is third in a series. I started by attempting to improve Solarized a little, then tried to make it significantly better by redoing everything, and in this post I simplify it.

Ah, I could not stand wasting hex digits on the shades of gray! This new set of colours doesn’t have the smooth hue transition – which is impossible to achieve in 4 bits per channel – but they work fine anyway: the dark ones have OKLCH hue 196 while the light ones have hue 107, so it’s basically dual-tone, except for the middle one, which suppresses both the red (like the dark colours) and the blue (like the light colours), leading to hue 145.

#022
#133
#355
#577
#898
#AA9
#CCB
#EED
#FFE

To work on both light and dark backgrounds, the red and magenta should be a tiny little brighter than the previous attempt. Against the respective backgrounds:

#E34
#C60
#A80
#890
#2A9
#28C
#77D
#C4B
#E34
#C60
#A80
#890
#2A9
#28C
#77D
#C4B
As usual, wanna explore the colours in Oklch?

The following are snippets you can paste to Huetone:

{ "name": "minisolar backgrounds"
, "hues":
  [
    { "name": "light"
    , "colors": [ "#FFE", "#EED", "#CCB", "#AA9", "#898", "#577", "#355", "#133", "#022" ]
    },
    { "name": "dark"
    , "colors": [ "#022", "#133", "#355", "#577", "#898", "#AA9", "#CCB", "#EED", "#FFE" ]
    }
  ]
}
{ "name": "minisolar colours"
, "hues":
  [
    { "name": "light"
    , "colors": [ "#FFE", "#577", "#E34", "#C60", "#A80", "#890", "#2A9", "#28C", "#77D", "#C4B" ]
    },
    { "name": "dark"
    , "colors": [ "#022", "#AA9", "#E34", "#C60", "#A80", "#890", "#2A9", "#28C", "#77D", "#C4B" ]
    }
  ]
}

A mini-post about a mini colour-scheme. That’s it! 🎉

Solar v2

2025-12-27

I’ve previously tweaked Solarized to my tastes. I feel that was a pretty clearcut improvement. This attempt is a lot braver:

  • Light and dark versions that both work.
  • Slightly higher contrast, both between the colours and with the background.
  • Warmer shades of gray, yay!
  • More custom colours (hijacks the colour cube 😱).

Light

Shades of gray, colours, optional backgrounds, and even more optional hi-vis backgrounds:

xoxoxo xoxoxo xoxoxo xoxoxo xoxoxo xoxoxo xoxoxo xoxoxo xoxoxo
xoxoxo
xoxoxo
xoxoxo
xoxoxo
xoxoxo
xoxoxo
xoxoxo
xoxoxo
xoxoxo
xoxoxo
xoxoxo
xoxoxo
xoxoxo
xoxoxo
xoxoxo
xoxoxo
xoxoxo
xoxoxo
xoxoxo
xoxoxo
xoxoxo
xoxoxo
xoxoxo
xoxoxo
xoxoxo
xoxoxo
xoxoxo
xoxoxo
xoxoxo
xoxoxo

Dark

xoxoxo xoxoxo xoxoxo xoxoxo xoxoxo xoxoxo xoxoxo xoxoxo xoxoxo
xoxoxo
xoxoxo
xoxoxo
xoxoxo
xoxoxo
xoxoxo
xoxoxo
xoxoxo
xoxoxo
xoxoxo
xoxoxo
xoxoxo
xoxoxo
xoxoxo
xoxoxo
xoxoxo
xoxoxo
xoxoxo
xoxoxo
xoxoxo
xoxoxo
xoxoxo
xoxoxo
xoxoxo
xoxoxo
xoxoxo
xoxoxo
xoxoxo
xoxoxo
xoxoxo
Interested in exploring the colours in OKLCH?

The following are snippets you can paste to Huetone. Dark-to-light and light-to-dark backgrounds:

Enable JavaScript to see this.

The various colours:

Enable JavaScript to see this.

Why?

I like the feel of the original Solarized colour theme. I just wanted more and better:

  • Slightly better separation of colours. The original had red and orange right next to each other.
  • Slightly higher contrast. It’s impossible to have good contrast when the colours are the same on both the dark and light backgrounds.
  • Coloured backgrounds. For like git history & stuff?

Why not selenized?

Selenized has had a lot of work put into it. However: I don’t like the green (too cold), and I did miss the orange and the purple. Yes it’s good to preserve the semantics of terminal colours. Had I not already worked around Solarized hijacking the “bright” colours, I would’ve preferred this.

What?

  • The “authoritative” colours are in the OKLCH colourspace, view source here or see GitHub. The hex colours are displayed for your convenience.
  • Kitty themes: light and dark. They hijack parts of the colour cube (colours 16-231) to provide coloured backgrounds.
  • A relatively short Vim/Neovim theme supporting Tree-sitter, Vimwiki, and some other stuff I use. Requires a true color terminal with set termguicolors.

The original Solarized (left) and my previous improvements (right):

solarized comparison

And these new ones, light (left) and dark (right):

solar2 comparison

Favourite Fonts For 2025

2025-12-12

I enjoy obsessing about typefaces. They’re everywhere, yet often somewhat invisible. I like free things, not just because they’re free (as in beer), but also because they can’t easily be taken away (as in freedom).

Alegreya

A workhorse:

Bricolage Grotesque

Recursive

  • Has a really cool interactive homepage.
  • Looks good both small and large.
  • Variable: mono (mono vs proportional & everything inbetween) casualness (casual vs linear & everything inbetween!), weight, slant, cursive.
  • Is on GitHub.

Honourable mentions

  • The IBM Plex family is great: the Sans and Mono are great and coherent and there’s also Serif.
  • Playfair Display is beautiful but somewhat single use (display!)

0 A.D.

2025-09-26

0 A.D. is an open source real time strategy game. This blog post is my review of a game I played. If you haven’t played 0 A.D., it might not make much sense.

Mostly my games used to last until… about the first fight. Then I’d either win or lose and that’d be it.

This game was different: much more exciting.

I haven’t played 0 A.D. for some years, and just tried a game against two AIs (let’s call them Red and Green). Medium difficulty, random civ, Petra bot, locked alliances. I upgrade everything, get to the third age, start building an army of Brythonic Champions, and still don’t properly meet my opponents. Perhaps a stray soldier or two. I’m mindful of the fact that there’s three of us: any two fighting, the third one is gonna be happy.

Red starts expanding near me, so I destroy some of his structures and prepare for a fight. At this point my main force is 15 champions, 25 slingers, and a hero. Red starts trickling units and my slingers mostly take them out, so the champions stay healthy. It feels like I’m massacring him, so I take my force of 25 champions, 25 slingers, and a hero to attack his nearby civic center and… fail spectacularly. It quickly becomes obvious this isn’t gonna work, the majority of my force returns to heal, alive but weakened. I set up a small trap for the pursuers: I put 25 slingers on a cliff overlooking the passage through which they’re pursuing me.

Meanwhile, on the other side, Green has massacred my outpost with perhaps 30 slingers harvesting metal, and destroyed some houses. Sucks, I need metal, and I liked my slingers. I repell him at some losses and promptly build a couple more towers to slow down future attacks.

I want to finish off Red first: he’s nearer and I feel I’ve already softened him up. Again I try to take over his civic center (50 champs, 50 slingers, hero) and again I fail. My retreating force gets further whopped by Green, but by that points I’m near home and manage to fight him off, navigating him right between my towers and a fortress. I recuperate, give up on trying to retake the civic center, send 5 rams with my 50 champs, 50 slingers… and… after some back and forth - I don’t want to lose too many units - I finally erase Red off the map!

Meanwhile, my defense against Green counts some 40 champions, the hero, and lots of towers. Alas, this is not enough! Green attacks with some 100 units. Meanwhile, my main force, with 60 champions and 60 slingers, is accross the map, having just finished whopping Red. Green absolutely thrashes my defense, the towers, and takes over my starting civic centre. I seriously mishandled the defense: the horses just take out my groups of 30 slingers one after the other without much problem. My main force attacks Green’s main settlement, as it’s too far to go back. This attack fails. I send them – weakened – to retake my starting civic center. Unfortunately, my towers are no longer my towers, and my main force gets annihilated.

At this point I have zero units, Red’s starting civic center, and, due to my bad resource management, a whole lot of resources. I max out unit production everywhere I can, and with significant losses and the help of some towers (bless you, Red!), fend off the Green attack wave. But what are losses to someone who has just lost all their units?

I destroy Green’s civic center perched up on a central mountain accessible only from my side (the map is Ambush, did I forget to tell you?) with two nearby metal mines. This is strategically very important to occupy, as will become clear later.

I go rebuild my starting civic center and reclaim the towers. This leads to a loss of a small army and my last hero. Did I tell you I burned through three heroes already? So, Green takes over that. I just built him a civic center!

I build 3 towers on the central mountain. Meanwhile, my economy is doing great. I managed to make use of most of the resources I saved up, built up a new workforce, and even a small army. It’s pretty clear my attacks just won’t do in this situation. Fortunately, the AI has an easily-exploitable problem: once it starts attacking you at one spot, it tends to direct all the attacks there. I put 50 slingers on the mountain next to a passage and 50 champions at the end of the passage. To get to my slingers, the enemy has to first withstand the slinger fire, then – already weakened – get through the champions. Fortunately the AI really wants to get to my slingers, and really wants to use the shortest path to do so. After easily fighting off several waves of the same attack, I go destroy Green and win the game.

Vibe coding two layer go

2025-08-30

This is it, the year is 2025 and vibe coding is upon us!

I’ve been using LLMs for programming for some time: they help me choose the right high level architecture, and they implement well-defined self-contained functions for me. I don’t trust an LLM to make changes to my code on its own.

Rysio from LSG independently invented two layer go. Two go boards above each other, connected. A regular stone has 5 liberties (4 neighbors + 1 from the other layer), the border has 4 liberties, and the corner has 3. It plays much better than full-3D go, which suffers from too much connectivity.

I wanted to implement it, but Omi said I should just vibe code it. Well, it worked. Here is my transcript:

“two layer” game of go, dimensions 11x11x2. Ideally something on the browser, front-end only, no backend. 3d representation one can rotate, click places stones of alternating colours, normal go rules apply

I randomly chose v0 from Vercel, which one-shotted a fully working, playable version in a couple of seconds. Some ~300 lines of TypeScript.

Amazing actually! Three changes I’d like to make:

  1. The menu covers most of the screen on mobile. Make it collapsible by a button.
  2. Hard to click: make the clickable spots bigger
  3. Connect the upper and the lower layer by lines (same as there are lines connecting the spots on the upper and lower layers themselves)

That worked too.

Again, amazing. One more little thing: can you please make the distance between the layers the same as the distance between other neighboring points? I just want the distances to be equal…

And that, too. Here it changed the opacity of some of the lines for a mysterious reason, but that was pretty simple to fix.

It works, it’s done: No side project for me! Vercel even hosts it for me.

Here is the fully playable two-layer go and the chat that led to it (which I suppose you can fork or something).

Keyboards

2025-04-02

Why is the Czech keyboard such a mess? QWERTZ/QWERTY is still a topic. The letters with the diacritical marks (ěščřžýáíé) are on the numeric top row, in place of 234567890. All the other stuff is shuffled around compared to the US English keyboard.

Everyone suffers: the “regular people” use QWERTZ. Many computer literate people prefer QWERTY, and some prefer the English keyboard and type without diacritics. Sit a random Czech at a random Czech keyboard and it’s likely they’ll feel lost.

Why don’t we learn from Poland? the 40 million Poles use a simple superset of the US English keyboard. From Wikipedia:

[T]he “Polish programmer’s” layout has become the de facto standard, used on virtually all computers sold on the Polish market.

Most computer keyboards in Poland are laid out according to the standard US visual and functional layout. Polish diacritics are accessed by using the AltGr key with a corresponding similar letter from the base Latin alphabet. Normal capitalization rules apply with respect to Shift and Caps Lock keys. For example, to enter “Ź”, one can type ⇧ Shift+AltGr+X with Caps Lock off, or turn on Caps Lock and type AltGr+X.

No wonder Poland has been experiencing the biggest economic boom in Europe for the past 20 years. Without keyboard woes, they are supremely productive.

When I started working in Czechia again, I was quickly overwhelmed by the constant need to switch between the Czech keyboard for messaging and Polish (ie English) for programming, the main problem was I’d always thought the other layout was being active. I didn’t find any of the choices from xkb config suitable. I stumbled upon CShack and liked it, with the only exception I want to keep my CapsLock, as I’ve mapped it to Ctrl, and use it all the time.

So now I can type á by pressing AltGr+a and Ó by pressing AltGr+Shift+o. I’ve reclaimed my sanity.