
Today's hack-a-day project was the Cookbook 2.0. I wrote a collection of my recipes in 2020. I've updated it with what I've learned in the last 5 years. Among other things, it has 57 new recipes.
You can read it online in a couple formats.
Enjoy!

Today's hack-a-day project was the Cookbook 2.0. I wrote a collection of my recipes in 2020. I've updated it with what I've learned in the last 5 years. Among other things, it has 57 new recipes.
You can read it online in a couple formats.
Enjoy!

Today's hack-a-day project was the Pokédex -- the fictional companion that tells you about pokemon in the game. My main goal was just to get the info into a reasonable database format, but along with way I built a little viewer too.
The plan is to make some kind of art game where you do pokemon fanart, a coloring book, or even a tracing game in the coming days. And now I'm ready, with art of each pokemon on hand.

Today's hack-a-day project was the German Language Reader. It shows you the text of "German Legends" by the Brothers Grimm. Yes, the same brothers known for "Grimm's Fairy Tales" and perhaps less-so, the first dictionary.
If you highlight a word, you get the definition in english.
You can try it online. As usual, source code is on github.
I had hoped to have a German mode, but the German wiktionary API has been down for a week!
If you're technically inclined, it's not too bad to substitute in your own book. You'll need it in TXT format (to extract the words), and HTML format (to display).
I'm happy with this project, although I am posting it on the 10th, quite a bit late.
Enjoy!
Yikes, been having some back pain, and the past few days it's been tougher to work. I've started four projects in four days, without too much to show for it.
Today's hack-a-day project was Reverse Vibe Coding. I sometimes use LLMs such as Claude for "vibe coding", mostly on throwaway type projects. It didn't seem fair for that to go only one way, so today I offered to vibe code for Claude -- it picks what I should make, and I code it up for Claude.
The result is the Conversation Flow Visualizer. This graphs when new topics come up in conversation, and what they are.
Frankly I think it's dumb and useless, but Claude is the boss, so there ya go! Can't pick who you work fo... okay, I guess I could this time.
In any case, it was pretty relaxing to be a junior dev and just do as I'm told for a bit, honestly. Easy win.
I honestly think this would be a good way to learn a new programming language or a new library.
Peace out!
The board game Go has been revolutionized in recent years by computer play. In 2016, AlphaGo beat Lee Sedol, a top Go player. This was the equivalent of what happened in Chess in 1997.
Since then, computers have continued to outstrip human players, but we have been learning a lot from Go engines. In this article I did some investigation using KataGo, which I understand to essentially be an open-source clone of the AlphaGo architecture.
This article assumes familiarity with the board game. If you're not familiar, I encourage you to give it a try sometime! Find a local try, or play online.
We have only one operation we can do. We can ask KataGo to analyze a position, and tell us how good that position is. That's the only operation we'll use in this article. And we're supposed to tell KataGo what the komi is.
KataGo returns two pieces of information for a position. An estimate of the score, and a percentage win chance.

The estimate of the score is determined (according to my very poor understanding), using an estimator which looks at the board, but doesn't try any moves. This is a fast, but low-quality metric.
On the other hand, the win rate is detemined by, simplifying some details, trying playing the game a bunch of times really fast and seeing how often black wins. It's slower, but more accurate.
Our first question is: How much should komi be?
Using only our one tool, let's figure out what KataGo thinks.
Well, in theory, what does a "good" komi mean? It means black and white should both win about 50% of the time. So let's just guess every possible komi, and find the one with the closest to 50% win rate.
Or, we could use the fast score estimator on an empty board with zero komi. If it thinks black is ahead by 6.0, maybe we could set komi to 6.0.
| size | komi estimate (winrate) | komi estimate (neural) |
|---|---|---|
| 3 | +14.0 | +4.4 |
| 4 | +0.5 | +2.4 |
| 5 | +25.5 | +23.3 |
| 6 | +3.0 | +4.3 |
| 7 | +8.0 | +8.7 |
| 8 | +9.0 | +6.6 |
| 9 | +6.0 | +6.0 |
| 10 | +6.0 | +5.6 |
| 11 | +6.0 | +5.5 |
| 12 | +6.0 | +5.5 |
| 13 | +6.0 | +5.6 |
| 14 | +6.0 | +5.5 |
| 15 | +6.0 | +5.7 |
| 16 | +6.0 | +5.8 |
| 17 | +6.0 | +5.9 |
| 18 | +6.0 | +6.1 |
| 19 | +6.5 | +6.2 |
It turns out both methods give similar results. We're going to use the win rate method going forward, because in general I've been told it's more accurate for many board positions.
In fact, we can use the same method to evaluate any board position accurately. We can figure out what komi would make that board position 50-50 for white or black to win. And then we can treat that as the "value" of the position.
For the rest of the article, we're going to simplify, and only ask the value of board positions. We don't care which method we use, but I'll mark the fast-and-simple method as "neural", and the winrate method as "komi" or "winrate" in pictures.
Our next question is, what are different starting moves worth? Well, let's just play every one and see what KataGo says the score is.


Note that all scores are relative to +6.5 for the empty board, which is why some values are negative.
Okay, easy enough. What about different numbers of handicap stones? Using standard placements, we get:
| size | handicap | value estimate (winrate) | value estimate (neural) |
|---|---|---|---|
| 19 | 1 | +6.5 | +6.2 |
| 19 | 2 | +20.0 | +19.2 |
| 19 | 3 | +32.5 | +32.5 |
| 19 | 4 | +47.5 | +46.6 |
| 19 | 5 | +59.5 | +58.3 |
| 19 | 6 | +72.5 | +71.8 |
| 19 | 7 | +86.0 | +85.1 |
| 19 | 8 | +100.5 | +100.3 |
| 19 | 9 | +115.5 | +114.7 |
| 13 | 1 | +6.0 | +5.6 |
| 13 | 2 | +19.5 | +18.6 |
| 13 | 3 | +32.5 | +30.7 |
| 13 | 4 | +48.0 | +47.4 |
| 13 | 5 | +59.0 | +58.6 |
| 13 | 6 | +75.0 | +74.5 |
| 13 | 7 | +87.0 | +84.0 |
| 13 | 8 | +100.5 | +96.1 |
| 13 | 9 | +109.5 | +102.3 |
| 9 | 1 | +6.0 | +6.0 |
| 9 | 2 | +16.0 | +16.0 |
| 9 | 3 | +27.5 | +27.1 |
| 9 | 4 | +75.0 | +53.5 |
| 9 | 5 | +74.5 | +79.0 |
Now let's make things more spicy. I keep winning every 1-stone game, but losing every 2-stone game. I want a 1.5 stone handicap. Well we can't add fractional stones, but we can look for something worth between 6.5 and 20 points.
Or, let's find something worth 0.0 points. I want a board position we can start with and not need that dumb komi rule.
Let's do the full analysis. Every possible starting board positions. Then we'll look for one that KataGo says is worth around... say, 12 points.
Of course, we can't really analyze every board position, so I just did ones with up to 2 stones. I included ones with white stones, because why not?
Here's what the ones with two black stones on 19x19 look like. It might take a bit to load, and you'll need to zoom in.
The full set of pictures is online.
19x19, positions closest to exact point values winrate
You can also get the raw score of 2-stone (and lower) positions on 9x9 and 19x19 boards. The code to do analysis and generate the pictures is on github, as are details on exact software settings used.
Thanks to Google for AlphaGo, and to lightvector for Katago (and Katago support).
Addendum.
After doing this project, I found it had already been done (better) at katagobooks.com. Apparently what I've done is called an "opening book", even if my goal was a bit different.
I'm not sure what the correct name for these parts organizers is. I usually call them "Tackle Boxes", even though that's not quite right. People tend to use them to store tiny screws and so on. I use them for electronics.
Today I designed little paper insert to go into them (in CAD), so I can label which electronics are which and find them more easily.

Sadly, US letter is not quite big enough to cover the whole box.
Printable PDF is available in A4 or in US letter. You can also see the SVG I was given originally which inspired this.
I recomment holding the paper in place with double-stick tape. If you don't have that, the usual trick is to roll normal tape into a small loop facing outwards.
P.S., to crop a PDF from A4 to US letter (assuming everything fits on both) without inflating the file size, use:
# Keeps all physical dimensions the same
pdfcrop --bbox "0 0 792 612" smallthing-on-A4.pdf smallthing-on-usletter.pdf
I got a copy of The Ultimate Micro-RPG Book. It's a nice book that has 40 different one-page and two-page RPGs by a wide variety of authors.
I'm running a local event where we play a new game, and I wanted to quickly find which RPGs in the book are suitable for the number of players at the table. Unfortunately the indices at the back do not include this option.
Here's my custom index for the book. You can print it easily if you want (9 pages, one for each of 1-9 players).
Sometimes I try to figure out what to do in a given week, and my first step is to think "what are my long term goals?". Then I realize I have no long-term goals. I sit down and think, "Hmm, what do I want to do with my life?" Eventually I give up, depressed, and go make breakfast or something.
Recently I tried going the other way, with much more success. I call this exercise telescoping. It takes 7 minutes, and is performed as follows:
I found this a bit enlightening. Here's an early example (I type fast):
Today
This Week
This Month
This Year
This Month
This Week
Today
I realized went from "Make progress on the zorchpad" to having a specific list of parts to order, and noticing I needed sleep. It helped me prioritize what I wanted to do.
I think it's more helpful to think about outcomes (What do I want to happen) rather than tasks (What do I need to do?), although I usually try to do both.
I did this two or three times, and kept finding it helpful. I decided to find out what happened if I did it every day.
Two weeks of doing it daily, I will report: it was neither super helpful nor a waste of time?
Basically, it was pretty helpful to think through things I hadn't. The main benefit of doing this every day was keeping long-term goals in sight, but I was no longer thinking through new things much.
The most surprising thing for me was that one minute quickly became too long. I reduced it to 30 seconds per step.
On the one hand, it's not that helpful. On the other hand, it takes 3.5 minutes, and it's a nice way to orient to the day. So, might or might not keep doing it.
The most useful part is that I can see my long term goals. Each day, they can shift. Do I want to keep working toward this thing? Have I had a long term goal that I haven't been working toward? Writing it anew each day is a much different feeling than glancing at a to-do list and either crossing it out or ignoring it (and feels much healthier).
Overall I'd recommend trying it at least once. I don't think it's for everyone, but I suspect it will help people like me that don't have clear goals.
As previously mentioned, I have switched off wordpress. Hopefully, you can't tell. It's meant to be behind-the-scenes.
The only change should be the new comment system. Feel free to try it out by commenting below. You could be the very first commenter!
The rest of this post is for anyone curious why and how, which I skipped last time.
If all is well, my blog looks exactly the same. All links should continue to work. The RSS feed should keep working. Basically it should be a behind-the-scenes change.
Why make this change, and why make it now?
Why not make the change?
Nonetheless, I forged on and decided to change. It was probably not worth the work, but since I put in the work, I'll at least share what I did.
Let's talk about how, rather than why, for the rest of the post. This took the better part of a month.
I thought about what I wanted to use. There were a few good options -- Jekyll and Hugo both came recommended, and I've used Jekyll before. They both use a format called frontmatter. Below is an example of a frontmatter document. The top is YAML and the bottom is HTML.
---
type: blog post
title: The worst types of pizza
---
<ol>
<li> Ham and Pineapple
<li> Anchovy
<li> Reheated in the microwave
</ol>
Basically, frontmatter consists of a "front" metadata section, in YAML or TOML or JSON, all of which are different ways of representing metadata. Metadata for a blog post includes things like the title of the blog post, when it was published and updated, and the author. And then below that, is a main content section in HTML or markdown. For a blog post, the main section is the text of the blog post.
I wasn't sure what engine I wanted to use, but I decided to use frontmatter. The content would just be the HTML, verbatim and unchanged from my existing blog. That way, everything would display right. I could write new posts in a new format. Old posts would be ugly behind the scenes, but it work, and I wouldn't have to migrate 200 posts.
I also really, really, didn't want to break the blog. I hate people who break a website changing things halfway. My work would only see the light of day once it was ready to wholly replace my existing blog. All the old links would work perfectly, even if I had to hand-code 200 redirects.
First, I wanted to have my existing posts in some format. Wordpress stores everything in a database. There are a couple options to get them out: - We could do a database dump. (This is very ugly. Don't do it.) - You can export them as an XML file. This is probably the best option. - You can download your website as HTML by crawling it. This is what I did, because I wanted to be sure I could have a blog that looked the same as my current one, and it seemed pretty foolproof.
So I had a big directory full of HTML blog posts, images, comments, etc. Next, I wrote an extractor. It looked at each file corresponding to a post, and grabbed the <article> element with the content of the post, together with any comments. It also extracted some relevant info like the author, publication date, title, and so on. It put them all together into a file. Now I had something that Jekyll and Hugo could use.
I took a look at Hugo. Wow, was it big. It supported YAML, TOML, JSON, HTML, Markdown. It had an asset pipeline. It had three different module systems to extend it. It did overlay filesystem mounts. Templating in Golang's templates. I slowly backed away.
I took a look at Jekyll--small, very opinioned. I generally like that in software. But, absolutely no customization. You had to put everything in a folder called _posts, and the publication date had to be the first part of the name. YAML only for the top. Etc. It seemd good, but I wasn't quite feeling it.
I decided I would roll my own. This was a small project. I only wanted a very limited set of functionality.
I wrote a template. It was an HTML page with a hole in it. You put the blog article HTML in the hole, and you got a finished HTML document. Looked fine. I used mustache from the templating, because I remembered liking it in the past. I got a blog showing. It looked great. It loaded lots of files (like icons images and styling) from the live site, rather than having a local copy. Most of the links went to the live site too.
I converted all the links. I wrote a checker to search for dead links. I decided to generate a page for each tag, since those would change over time. I noticed the tag pages and the post pages had most of the layout in common, so I factored that out. I discovered my python mustache library didn't do "factoring out", and only the javascript library did. I realized I had never liked mustache--I had been thinking of handlebars or spacebars. I decided to put it off--switching templating engines was easy, but it's better not to switch horses mid-stream. I factored out the tag cloud. I got the number of dead links down to just the page of links by one author and the RSS feed. I generated those too. I started generating more of the blog post--the title and author and comments section, too. The HTML shrunk. I had a working version.
I started feeling super disheartened. This was a giant mess. I just wasn't feeling motivated. I took a step back. Was it the work? No, I decided. It was that I didn't want to put in a ton of work, to get a system I wasn't all that happy with. Wordpress was already okay. It wasn't perfect, but it was alright. If I was going to put in work, I wanted the new system to be better. I wanted... I think I wanted to convert the old HTML posts to markdown?
Hoo boy. That was going to be a lot of work.
I took a look around. A year ago (the last time I saw a gorgeous Amal Murali blog post), I had tried a wordpress conversion. I had tried wordpress-export-to-markdown, but I had remembered not liking the output that much. Things had been missing. They hadn't looked right. But it done 80% of things correctly. I checked what it used. Hmm, turndown. A javascript tool to convert HTML to markdown. Sounded promising.
I converted everything to markdown. I took at look at the output. Seemed... reasonable. I'd have to take a look before I decided anything past that. So I needed a tool to convert markdown back to HTML. I was using Python, so I picked markdown2 -- the markdown (1?) page seemed pretty.. theoretical. User comfort seemed like maybe a fourth priority. It hadn't been updated in a few years. markdown2 seemed to care about speed and user comfort. It had lots of plugins. It had been updated last week, though it looked like they hadn't done anything major in a couple years. I gave it a try.
I took HTML, converted it to markdown, converted it back to HTML, and looked at the result. It was... eh. It had some of the same content, but it didn't look quite right. I looked at the HTML. Oh, I had forgotten to wrap it in an <article> tag with all those special wordpress classes. I gave it another try. WOW! That looked almost identical. I made a webpage to look at them side-by-side.

Okay, I could do this. There was going to be a list of problems, but I could get through them one by one.
I started looking at articles. Okay, this was missing a class. Galleries were just a series of images now. iframes were being dropped. This was all stuff I could fix. Some of it would be problems converting HTML to markdown. Cases of stripping vital information was especially problematic, because I couldn't fix it later.
Some problems happened when converting markdown to HTML--code blocks inside lists disappeared and became regular text. I started looking into fixes. I was annoyed how hard it was to extend Turndown. I considered writing my own HTML to markdown converter. That was the easy direction--anyone can parse HTML, there are libraries for it. Outputting is easy in any language. Wait, I thought. Turndown would disappear in the final version. Once I had converted the old HTML, that was it. How many problems were there, really? If it was just a few articles, I should fix it by hand instead. That would be easier. I decided I'd wait until I had a better overview.
Other problems happened when converting markdown back to HTML. Parsing markdown would be a nightmare, so I crossed my fingers and prayed I wouldn't have to. I hoped markdown2 was easy to extend. I started thinking with distaste about if I would have to... rewrite the HTML output shudder. I put things off--disappearing information was more important.
I decided to take stock. How would I tell if I was making progress? What if fixing one thing broke another? I had some kind of visual diff tool in mind. If the HTML and markdown versions looked the same, that was good enough for me. But would they? I don't care about little changes. One font slightly different, a section a few pixels to the left. I was worried I would compare the before and after, and none of them would match. I don't know how to tell a computer to ignore that stuff. Oh well, I'd check. Maybe it would work.
I ran a first check using puppeteer to take Chrome screenshots. 24% of posts were identical, right out the gate. That was more than 0%. That meant that yes, this method would actually let me make a TODO list. 0% would have been bad. OK. I started opening up articles. Yes, they actually looked different. It wasn't a few pixels. Every page I opened, seemed to have genuine differences I wanted to fix.
I started fixing the problems. Some big problems got fixed. Smaller ones started cropping up. The first one I found was these. They were comparing different. Was that right?

I stared. I saw nothing. I visually showed the difference. The fonts were highlighted in red. Was it a font issue? I looked at the HTML. Oh, one gray was 10% lighter. Should I fix it? No wait, I didn't want things to be pixel-perfect identical. That was just a tool to measure how close I was to done, let's not lose track of the actual goal. Hmm.
I was starting to feel burnt-out. I wasn't sure where to go next. I talked to friends. I ended up using a heuristic to rank the pages from most to least similar. I'd tackle the big problems. As it happened, some contractors were jackhammering my basement for a few hours, so I had time to kill where I couldn't focus anyway. I opened all ~100 blog posts in chromium, and make little notes about each problem before I closed the tab. If I would be fine not fixing a problem, I didn't write it down. If I saw the same problem twice, I'd add a little + mark next to it. At the end, I had some problems with a lot of + marks next to them. Those were the ones I'd tackle first. Maybe more importantly, I had a good idea of the total amount of work. It was maybe 10 or 20 things to fix, even if I was very fussy. I was okay with that. I could do it.
I went in and started fixing. I found out that Turndown was pretty unmaintained, just like I suspected. I made about 5 PRs--none had any response, so I used a local fork. pyhon-markdown2 usually worked. Every time I thought I found a bug, it was my fault--I hadn't understood something about the nuances of markdown. In one case a bug was real but already fixed in a newer version.
After fixing a dozen problems, I was done. I took a look through the articles again. Most of them looked fine now. I generated the markdown one more time, and then hand-fixed 5-10 articles with problems. I filed fixed articles into a "finished" folder, so they wouldn't be overwritten if I changed my mind and did an automated rebuild.
It was done. I looked, and looked again. Then I deleted all the HTML sources. The side-by-side view. The visual comparison tools. The side-by-side view. The dead link checker. The crawler that extracted the original HTML. I was left with a single tool--it took markdown, and generated a blog. It was tiny again. I rejoiced, and took a well-needed break.
At this point, I had a working blog. Posts were YAML frontmatter, and markdown content. I could write new posts easily in markdown, and all my old posts were in markdown too. I was pretty happy.
I had two more big tasks. One, which I'm punting indefinitely, is to re-style the blog. My current approach is to just have a copy of the old wordpress CSS in one file. It's 7,838 lines long, which is too long. I could reduce it, but it's probably equally reasonable to just make an entirely new stylesheet from scratch. I'm not sure whether old articles will keep the old stylesheet. Probably yes, just to avoid breaking anything. That is... not urgent. I'll do it sometime.
The other part, which I did care about, was to get comments working again. I looked around at a few static site commenting options, and settled on Isso. The user-friendly front page encouraged me. It didn't require registration, it had email moderation where you click a link to approve a comment, comments could use markdown, there was no database setup. And it supported wordpress comment import (although I didn't do this actually).
Great! Now how to install? Oh... the debian package is discontinued? Okay, it was actually a bit of work.
I started by installing isso.
mkdir /var/www/isso /var/log/isso /var/isso
cd /var/www/isso
python3 -m venv .
source bin/activate
pip install isso gevent
sudo ln -s /var/www/isso/bin/isso /bin/isso
chown -R isso:isso /var/www/isso /var/log/isso /var/isso
I added a config file (/etc/isso.cfg)
[general]
dbpath = /var/isso/comments.db
host = https://blog.za3k.com
notify = smtp
log-file = /var/log/isso/isso.log
[moderation]
enabled = true
purge-after = 10000d
[server]
listen = http://localhost:9007
public-endpoint = https://blog.za3k.com/comments
[smtp]
host = smtp.za3k.com
to = za3k@za3k.com
from = isso@blog.za3k.com
username = za3k@za3k.com
password = hunter1
[hash]
salt = <anything non-default>
I didn't bother with RSS -- no one reads an RSS feed of comments, and they get included in the RSS feed of posts.
I ran isso by hand:
sudo -u isso /bin/isso &
tail -f /var/log/isso
Added an nginx frontend proxy:
# Run as isso.service
upstream isso {
server 127.0.0.1:9007;
}
server {
listen [::]:443 ssl;
server_name blog.za3k.com;
[... rest of blog.za3k.com ... ]
# comments
location = /comments {
return 302 /comments/;
}
location = /comments/ {
proxy_pass http://isso/;
}
location /comments/ {
proxy_pass http://isso/;
}
}
Added some code to the static generation:
<script src="https://blog.za3k.com/comments/js/embed.min.js"></script>
<section id="isso-thread">
<noscript>Javascript needs to be activated to view comments.</noscript>
</section>
And debugged a few errors here and there. Then I added a systemd unit, which I enabled and started:
[Unit]
Description=isso commenting system
[Service]
ExecStart=/bin/isso
Restart=on-failure
TimeoutSec=1
User=isso
[Install]
WantedBy=multi-user.target
Yay! Comments are working again. And with that, my conversion is complete.
I recently tried to buy a 3-ring binder, and I was annoyed didn't list spacings on paper OR binders. This is my attempt to write a reference for future searchers.
The spacing between rings seems to be standard. The ring size itself isn't standard. Holes vary from 6-8mm. I live in the United States -- different binders may be standard where you live.
Here are the spacings I found. If you encounter a 3-ring binder not listed, or pre-punched paper that doesn't match the below, please email me with the measurements and brand name.
Paper sizes:
US Letter, 3-ring
These are the "standard" 3-ring binders in the USA.
108mm hole spacing
A5, 3-ring
In Staples, I found these sold as "mini" binders.
70mm hole spacting
A5, 6-ring
19mm hole pitch, 70mm central gap
A6, 6-ring
19mm hole pitch, 40mm central gap
Filofax, 6-ring
19mm hole pitch, 50.8mm central gap