From 09e410b21358e1475261499ccf6a75fac2da12d4 Mon Sep 17 00:00:00 2001 From: Jan Tuomi Date: Mon, 5 Jan 2026 21:15:44 +0200 Subject: Refactor and colocate assets --- content/posts/atk16-my-homegrown-computer.md | 17 - .../atk16-text-mode.png | Bin 0 -> 427840 bytes content/posts/atk16-my-homegrown-computer/index.md | 17 + content/posts/cassettes.md | 28 - content/posts/cassettes/cassette_player.jpeg | Bin 0 -> 3747638 bytes content/posts/cassettes/index.md | 28 + content/posts/deploying-the-garden.md | 38 -- content/posts/deploying-the-garden/index.md | 38 ++ content/posts/deploying-the-garden/repo_langs.png | Bin 0 -> 25064 bytes content/posts/diddle-event-scheduler.md | 20 - .../diddle-event-scheduler/diddle_screenshot.png | Bin 0 -> 100453 bytes content/posts/diddle-event-scheduler/index.md | 20 + content/posts/eurobsdcon-2025-conf-notes.md | 89 --- .../eurobsdcon_2025_binders.jpeg | Bin 0 -> 3375983 bytes .../eurobsdcon_2025_cevapi.jpeg | Bin 0 -> 3111850 bytes .../eurobsdcon_2025_group.jpeg | Bin 0 -> 621216 bytes .../eurobsdcon_2025_hallway.jpeg | Bin 0 -> 4168772 bytes content/posts/eurobsdcon-2025-conf-notes/index.md | 89 +++ content/posts/home-server-part-1.md | 78 --- .../posts/home-server-part-1/3-2-1-Backup-Rule.png | Bin 0 -> 34158 bytes .../posts/home-server-part-1/aliexpress_minipc.png | Bin 0 -> 793558 bytes .../posts/home-server-part-1/do-june-billing.png | Bin 0 -> 133566 bytes content/posts/home-server-part-1/index.md | 78 +++ .../home-server-part-1/old-man-yells-at-cloud.png | Bin 0 -> 1059057 bytes content/posts/home-server-part-2.md | 91 --- .../posts/home-server-part-2/aliexpress_minipc.png | Bin 0 -> 793558 bytes .../aliexpress_minipc_results.png | Bin 0 -> 1360509 bytes content/posts/home-server-part-2/index.md | 91 +++ content/posts/home-server-part-3.md | 77 --- content/posts/home-server-part-3/index.md | 77 +++ .../home-server-part-3/pursotin_fastfetch.png | Bin 0 -> 285448 bytes .../posts/home-server-part-3/pursotin_raidz.svg | 1 + content/posts/home-server-part-4.md | 124 ---- content/posts/home-server-part-4/index.md | 124 ++++ .../posts/home-server-part-4/pursotin_network.svg | 1 + content/posts/it-doesnt-mean-that.md | 42 -- content/posts/it-doesnt-mean-that/index.md | 40 ++ .../posts/it-doesnt-mean-that/wikimedia_monad.svg | 1 + content/posts/now-2024-fall.md | 88 --- content/posts/now-2024-fall/diffuser_1.jpg | Bin 0 -> 3447758 bytes content/posts/now-2024-fall/diffuser_2.jpg | Bin 0 -> 3239064 bytes content/posts/now-2024-fall/diffuser_3.jpg | Bin 0 -> 3723759 bytes content/posts/now-2024-fall/index.md | 88 +++ content/posts/now-2024-spring.md | 87 --- content/posts/now-2024-spring/index.md | 87 +++ content/posts/now-2024-spring/jojo-in-snow.webm | Bin 0 -> 1508041 bytes content/posts/now-2024-summer.md | 56 -- content/posts/now-2024-summer/2024-wines.jpg | Bin 0 -> 3095038 bytes content/posts/now-2024-summer/aws-exam-drink.jpeg | Bin 0 -> 2606811 bytes content/posts/now-2024-summer/index.md | 56 ++ content/posts/now-2025-fall.md | 90 --- content/posts/now-2025-fall/index.md | 90 +++ content/posts/now-2025-fall/valokate_1.jpg | Bin 0 -> 3726158 bytes content/posts/now-2025-fall/valokate_2.jpg | Bin 0 -> 1931666 bytes content/posts/now-2025-fall/villa.jpeg | Bin 0 -> 2572301 bytes content/posts/now-2025-summer.md | 114 ---- content/posts/now-2025-summer/index.md | 114 ++++ content/posts/now-2025-summer/kenrokuen.jpg | Bin 0 -> 8081996 bytes content/posts/now-2025-summer/samurai_garden.jpeg | Bin 0 -> 6622434 bytes content/posts/now-2025-summer/wagyu_grill.jpeg | Bin 0 -> 3270292 bytes content/posts/now-2025-winter.md | 129 ----- .../posts/now-2025-winter/2025_wrapped_genres.jpeg | Bin 0 -> 173496 bytes .../now-2025-winter/backdoor_feature_done.jpeg | Bin 0 -> 3525718 bytes .../now-2025-winter/backdoor_feature_wip.jpeg | Bin 0 -> 3340312 bytes content/posts/now-2025-winter/dopamine_office.jpeg | Bin 0 -> 3272619 bytes content/posts/now-2025-winter/index.md | 129 +++++ content/posts/now-2025-winter/muumimusiikkia.jpeg | Bin 0 -> 2951215 bytes content/posts/now-2025-winter/taulu_koira.jpeg | Bin 0 -> 2778093 bytes content/posts/qcon-london-2024-takeaways.md | 112 ---- .../fish-and-chips-duke-of-argyll.jpg | Bin 0 -> 2997683 bytes content/posts/qcon-london-2024-takeaways/index.md | 112 ++++ .../london-from-above.jpg | Bin 0 -> 3041148 bytes content/posts/subjective-review-miru.md | 73 --- .../hinokodo-static-abyss.png | Bin 0 -> 2457949 bytes content/posts/subjective-review-miru/index.md | 73 +++ .../subjective-review-miru/miru_gameplay.jpeg | Bin 0 -> 2317180 bytes .../miru_time_to_kill_triple.jpg | Bin 0 -> 1559052 bytes content/posts/theres-an-rss-feed-now.md | 10 - content/posts/theres-an-rss-feed-now/index.md | 10 + .../posts/theres-an-rss-feed-now/internet_surf.png | Bin 0 -> 394047 bytes content/posts/unfold.md | 42 -- content/posts/unfold/index.md | 42 ++ content/posts/unfold/indieweb_presentation.png | Bin 0 -> 333070 bytes content/posts/unfold/reveal-js-logo.png | Bin 0 -> 85023 bytes content/projects/ATK16.md | 627 --------------------- content/projects/_index.md | 8 +- content/projects/atk16/block-diagram.svg | 1 + content/projects/atk16/index.md | 627 +++++++++++++++++++++ content/projects/atk16/text-mode.png | Bin 0 -> 427840 bytes content/projects/diddle.md | 25 - content/projects/diddle/diddle_screenshot.png | Bin 0 -> 100453 bytes content/projects/diddle/index.md | 25 + static/files/2024-wines.jpg | Bin 3095038 -> 0 bytes static/files/2025_wrapped_genres.jpeg | Bin 173496 -> 0 bytes static/files/3-2-1-Backup-Rule.png | Bin 34158 -> 0 bytes static/files/aliexpress_minipc.png | Bin 793558 -> 0 bytes static/files/aliexpress_minipc_results.png | Bin 1360509 -> 0 bytes static/files/atk16-block-diagram.svg | 1 - static/files/atk16-text-mode.png | Bin 427840 -> 0 bytes static/files/aws-exam-drink.jpeg | Bin 2606811 -> 0 bytes static/files/backdoor_feature_done.jpeg | Bin 3525718 -> 0 bytes static/files/backdoor_feature_wip.jpeg | Bin 3340312 -> 0 bytes static/files/cassette_player.jpeg | Bin 3747638 -> 0 bytes static/files/diddle_screenshot.png | Bin 100453 -> 0 bytes static/files/diffuser_1.jpg | Bin 3447758 -> 0 bytes static/files/diffuser_2.jpg | Bin 3239064 -> 0 bytes static/files/diffuser_3.jpg | Bin 3723759 -> 0 bytes static/files/do-june-billing.png | Bin 133566 -> 0 bytes static/files/dopamine_office.jpeg | Bin 3272619 -> 0 bytes static/files/eurobsdcon_2025_binders.jpeg | Bin 3375983 -> 0 bytes static/files/eurobsdcon_2025_cevapi.jpeg | Bin 3111850 -> 0 bytes static/files/eurobsdcon_2025_group.jpeg | Bin 621216 -> 0 bytes static/files/eurobsdcon_2025_hallway.jpeg | Bin 4168772 -> 0 bytes static/files/fish-and-chips-duke-of-argyll.jpg | Bin 2997683 -> 0 bytes static/files/hinokodo-static-abyss.png | Bin 2457949 -> 0 bytes static/files/indieweb_presentation.png | Bin 333070 -> 0 bytes static/files/internet_surf.png | Bin 394047 -> 0 bytes static/files/jojo-in-snow.webm | Bin 1508041 -> 0 bytes static/files/kenrokuen.jpg | Bin 8081996 -> 0 bytes static/files/london-from-above.jpg | Bin 3041148 -> 0 bytes static/files/miru_gameplay.jpeg | Bin 2317180 -> 0 bytes static/files/miru_time_to_kill_triple.jpg | Bin 1559052 -> 0 bytes static/files/muumimusiikkia.jpeg | Bin 2951215 -> 0 bytes static/files/old-man-yells-at-cloud.png | Bin 1059057 -> 0 bytes static/files/pursotin_fastfetch.png | Bin 285448 -> 0 bytes static/files/pursotin_network.svg | 1 - static/files/pursotin_raidz.svg | 1 - static/files/repo_langs.png | Bin 25064 -> 0 bytes static/files/reveal-js-logo.png | Bin 85023 -> 0 bytes static/files/samurai_garden.jpeg | Bin 6622434 -> 0 bytes static/files/taulu_koira.jpeg | Bin 2778093 -> 0 bytes static/files/valokate_1.jpg | Bin 3726158 -> 0 bytes static/files/valokate_2.jpg | Bin 1931666 -> 0 bytes static/files/villa.jpeg | Bin 2572301 -> 0 bytes static/files/wagyu_grill.jpeg | Bin 3270292 -> 0 bytes static/files/wikimedia_monad.svg | 1 - 136 files changed, 2063 insertions(+), 2065 deletions(-) delete mode 100644 content/posts/atk16-my-homegrown-computer.md create mode 100644 content/posts/atk16-my-homegrown-computer/atk16-text-mode.png create mode 100644 content/posts/atk16-my-homegrown-computer/index.md delete mode 100644 content/posts/cassettes.md create mode 100644 content/posts/cassettes/cassette_player.jpeg create mode 100644 content/posts/cassettes/index.md delete mode 100644 content/posts/deploying-the-garden.md create mode 100644 content/posts/deploying-the-garden/index.md create mode 100644 content/posts/deploying-the-garden/repo_langs.png delete mode 100644 content/posts/diddle-event-scheduler.md create mode 100644 content/posts/diddle-event-scheduler/diddle_screenshot.png create mode 100644 content/posts/diddle-event-scheduler/index.md delete mode 100644 content/posts/eurobsdcon-2025-conf-notes.md create mode 100644 content/posts/eurobsdcon-2025-conf-notes/eurobsdcon_2025_binders.jpeg create mode 100644 content/posts/eurobsdcon-2025-conf-notes/eurobsdcon_2025_cevapi.jpeg create mode 100644 content/posts/eurobsdcon-2025-conf-notes/eurobsdcon_2025_group.jpeg create mode 100644 content/posts/eurobsdcon-2025-conf-notes/eurobsdcon_2025_hallway.jpeg create mode 100644 content/posts/eurobsdcon-2025-conf-notes/index.md delete mode 100644 content/posts/home-server-part-1.md create mode 100644 content/posts/home-server-part-1/3-2-1-Backup-Rule.png create mode 100644 content/posts/home-server-part-1/aliexpress_minipc.png create mode 100644 content/posts/home-server-part-1/do-june-billing.png create mode 100644 content/posts/home-server-part-1/index.md create mode 100644 content/posts/home-server-part-1/old-man-yells-at-cloud.png delete mode 100644 content/posts/home-server-part-2.md create mode 100644 content/posts/home-server-part-2/aliexpress_minipc.png create mode 100644 content/posts/home-server-part-2/aliexpress_minipc_results.png create mode 100644 content/posts/home-server-part-2/index.md delete mode 100644 content/posts/home-server-part-3.md create mode 100644 content/posts/home-server-part-3/index.md create mode 100644 content/posts/home-server-part-3/pursotin_fastfetch.png create mode 100644 content/posts/home-server-part-3/pursotin_raidz.svg delete mode 100644 content/posts/home-server-part-4.md create mode 100644 content/posts/home-server-part-4/index.md create mode 100644 content/posts/home-server-part-4/pursotin_network.svg delete mode 100644 content/posts/it-doesnt-mean-that.md create mode 100644 content/posts/it-doesnt-mean-that/index.md create mode 100644 content/posts/it-doesnt-mean-that/wikimedia_monad.svg delete mode 100644 content/posts/now-2024-fall.md create mode 100644 content/posts/now-2024-fall/diffuser_1.jpg create mode 100644 content/posts/now-2024-fall/diffuser_2.jpg create mode 100644 content/posts/now-2024-fall/diffuser_3.jpg create mode 100644 content/posts/now-2024-fall/index.md delete mode 100644 content/posts/now-2024-spring.md create mode 100644 content/posts/now-2024-spring/index.md create mode 100644 content/posts/now-2024-spring/jojo-in-snow.webm delete mode 100644 content/posts/now-2024-summer.md create mode 100644 content/posts/now-2024-summer/2024-wines.jpg create mode 100644 content/posts/now-2024-summer/aws-exam-drink.jpeg create mode 100644 content/posts/now-2024-summer/index.md delete mode 100644 content/posts/now-2025-fall.md create mode 100644 content/posts/now-2025-fall/index.md create mode 100644 content/posts/now-2025-fall/valokate_1.jpg create mode 100644 content/posts/now-2025-fall/valokate_2.jpg create mode 100644 content/posts/now-2025-fall/villa.jpeg delete mode 100644 content/posts/now-2025-summer.md create mode 100644 content/posts/now-2025-summer/index.md create mode 100644 content/posts/now-2025-summer/kenrokuen.jpg create mode 100644 content/posts/now-2025-summer/samurai_garden.jpeg create mode 100644 content/posts/now-2025-summer/wagyu_grill.jpeg delete mode 100644 content/posts/now-2025-winter.md create mode 100644 content/posts/now-2025-winter/2025_wrapped_genres.jpeg create mode 100644 content/posts/now-2025-winter/backdoor_feature_done.jpeg create mode 100644 content/posts/now-2025-winter/backdoor_feature_wip.jpeg create mode 100644 content/posts/now-2025-winter/dopamine_office.jpeg create mode 100644 content/posts/now-2025-winter/index.md create mode 100644 content/posts/now-2025-winter/muumimusiikkia.jpeg create mode 100644 content/posts/now-2025-winter/taulu_koira.jpeg delete mode 100644 content/posts/qcon-london-2024-takeaways.md create mode 100644 content/posts/qcon-london-2024-takeaways/fish-and-chips-duke-of-argyll.jpg create mode 100644 content/posts/qcon-london-2024-takeaways/index.md create mode 100644 content/posts/qcon-london-2024-takeaways/london-from-above.jpg delete mode 100644 content/posts/subjective-review-miru.md create mode 100644 content/posts/subjective-review-miru/hinokodo-static-abyss.png create mode 100644 content/posts/subjective-review-miru/index.md create mode 100644 content/posts/subjective-review-miru/miru_gameplay.jpeg create mode 100644 content/posts/subjective-review-miru/miru_time_to_kill_triple.jpg delete mode 100644 content/posts/theres-an-rss-feed-now.md create mode 100644 content/posts/theres-an-rss-feed-now/index.md create mode 100644 content/posts/theres-an-rss-feed-now/internet_surf.png delete mode 100644 content/posts/unfold.md create mode 100644 content/posts/unfold/index.md create mode 100644 content/posts/unfold/indieweb_presentation.png create mode 100644 content/posts/unfold/reveal-js-logo.png delete mode 100644 content/projects/ATK16.md create mode 100644 content/projects/atk16/block-diagram.svg create mode 100644 content/projects/atk16/index.md create mode 100644 content/projects/atk16/text-mode.png delete mode 100644 content/projects/diddle.md create mode 100644 content/projects/diddle/diddle_screenshot.png create mode 100644 content/projects/diddle/index.md delete mode 100644 static/files/2024-wines.jpg delete mode 100644 static/files/2025_wrapped_genres.jpeg delete mode 100644 static/files/3-2-1-Backup-Rule.png delete mode 100644 static/files/aliexpress_minipc.png delete mode 100644 static/files/aliexpress_minipc_results.png delete mode 100644 static/files/atk16-block-diagram.svg delete mode 100644 static/files/atk16-text-mode.png delete mode 100644 static/files/aws-exam-drink.jpeg delete mode 100644 static/files/backdoor_feature_done.jpeg delete mode 100644 static/files/backdoor_feature_wip.jpeg delete mode 100644 static/files/cassette_player.jpeg delete mode 100644 static/files/diddle_screenshot.png delete mode 100644 static/files/diffuser_1.jpg delete mode 100644 static/files/diffuser_2.jpg delete mode 100644 static/files/diffuser_3.jpg delete mode 100644 static/files/do-june-billing.png delete mode 100644 static/files/dopamine_office.jpeg delete mode 100644 static/files/eurobsdcon_2025_binders.jpeg delete mode 100644 static/files/eurobsdcon_2025_cevapi.jpeg delete mode 100644 static/files/eurobsdcon_2025_group.jpeg delete mode 100644 static/files/eurobsdcon_2025_hallway.jpeg delete mode 100644 static/files/fish-and-chips-duke-of-argyll.jpg delete mode 100644 static/files/hinokodo-static-abyss.png delete mode 100644 static/files/indieweb_presentation.png delete mode 100644 static/files/internet_surf.png delete mode 100644 static/files/jojo-in-snow.webm delete mode 100644 static/files/kenrokuen.jpg delete mode 100644 static/files/london-from-above.jpg delete mode 100644 static/files/miru_gameplay.jpeg delete mode 100644 static/files/miru_time_to_kill_triple.jpg delete mode 100644 static/files/muumimusiikkia.jpeg delete mode 100644 static/files/old-man-yells-at-cloud.png delete mode 100644 static/files/pursotin_fastfetch.png delete mode 100644 static/files/pursotin_network.svg delete mode 100644 static/files/pursotin_raidz.svg delete mode 100644 static/files/repo_langs.png delete mode 100644 static/files/reveal-js-logo.png delete mode 100644 static/files/samurai_garden.jpeg delete mode 100644 static/files/taulu_koira.jpeg delete mode 100644 static/files/valokate_1.jpg delete mode 100644 static/files/valokate_2.jpg delete mode 100644 static/files/villa.jpeg delete mode 100644 static/files/wagyu_grill.jpeg delete mode 100644 static/files/wikimedia_monad.svg diff --git a/content/posts/atk16-my-homegrown-computer.md b/content/posts/atk16-my-homegrown-computer.md deleted file mode 100644 index 6b9f583..0000000 --- a/content/posts/atk16-my-homegrown-computer.md +++ /dev/null @@ -1,17 +0,0 @@ ---- -title: ATK16 – My homegrown computer -date: 2024-05-01 -extra: - kind: project ---- - -![A screenshot of a text mode program displaying a work-in-progress hardware monitor](/files/atk16-text-mode.png) - -**ATK16** is a learning project I've been working on for some time. It started off as a 16-bit, reduced instruction set computer (RISC) CPU design but evolved into an entire computer with peripherals and software. - -Instead of repeating myself, I'm going to direct you to the [ATK16 project page](/projects/atk16). It's not complete by any means, but I've written a bunch about the project and my motivations already! [¹](#footnote) - ---- - -
-1. There's actually more text in there than in my bachelor's thesis. diff --git a/content/posts/atk16-my-homegrown-computer/atk16-text-mode.png b/content/posts/atk16-my-homegrown-computer/atk16-text-mode.png new file mode 100644 index 0000000..93c2752 Binary files /dev/null and b/content/posts/atk16-my-homegrown-computer/atk16-text-mode.png differ diff --git a/content/posts/atk16-my-homegrown-computer/index.md b/content/posts/atk16-my-homegrown-computer/index.md new file mode 100644 index 0000000..ba8bf84 --- /dev/null +++ b/content/posts/atk16-my-homegrown-computer/index.md @@ -0,0 +1,17 @@ +--- +title: ATK16 – My homegrown computer +date: 2024-05-01 +extra: + kind: project +--- + +![A screenshot of a text mode program displaying a work-in-progress hardware monitor](atk16-text-mode.png) + +**ATK16** is a learning project I've been working on for some time. It started off as a 16-bit, reduced instruction set computer (RISC) CPU design but evolved into an entire computer with peripherals and software. + +Instead of repeating myself, I'm going to direct you to the [ATK16 project page](/projects/atk16). It's not complete by any means, but I've written a bunch about the project and my motivations already! [¹](#footnote) + +--- + +
+1. There's actually more text in there than in my bachelor's thesis. diff --git a/content/posts/cassettes.md b/content/posts/cassettes.md deleted file mode 100644 index 5b8f6b6..0000000 --- a/content/posts/cassettes.md +++ /dev/null @@ -1,28 +0,0 @@ ---- -title: Cassettes -date: 2024-03-27 -extra: - kind: note ---- - -I like the idea of cassettes. They are cheap and analog, and there’s a culture of sharing and trading them. I like the sound of tape saturation and hysteresis. - -They feel much more approachable than vinyls. I dislike snobbery. - -## So I bought a cassette player - -{{ fig(src="/files/cassette_player.jpeg", alt="A Sony cassette recorder next to a pile of cassettes") }} - -It’s a Sony TCM-459V and it’s only a bit noisy and wobbly. It can record too. - -After listening to all of my tapes (not very many, see picture), A side and B side, I can safely say that I like cassettes. - -## Maintenance - -The manual says that the innards need cleaning (rubbing alcohol + q-tip) after every 10 hours of operation. I don’t think this unit has ever been cleaned. - -The belts might also need changing since they apparently go bad after a while. - -## Tapes catch on fire - -I read on the internet that peoples’ tapes have spontaneously combusted. This is a thing to avoid. diff --git a/content/posts/cassettes/cassette_player.jpeg b/content/posts/cassettes/cassette_player.jpeg new file mode 100644 index 0000000..6591cc4 Binary files /dev/null and b/content/posts/cassettes/cassette_player.jpeg differ diff --git a/content/posts/cassettes/index.md b/content/posts/cassettes/index.md new file mode 100644 index 0000000..92d3887 --- /dev/null +++ b/content/posts/cassettes/index.md @@ -0,0 +1,28 @@ +--- +title: Cassettes +date: 2024-03-27 +extra: + kind: note +--- + +I like the idea of cassettes. They are cheap and analog, and there’s a culture of sharing and trading them. I like the sound of tape saturation and hysteresis. + +They feel much more approachable than vinyls. I dislike snobbery. + +## So I bought a cassette player + +{{ fig(src="cassette_player.jpeg", alt="A Sony cassette recorder next to a pile of cassettes") }} + +It’s a Sony TCM-459V and it’s only a bit noisy and wobbly. It can record too. + +After listening to all of my tapes (not very many, see picture), A side and B side, I can safely say that I like cassettes. + +## Maintenance + +The manual says that the innards need cleaning (rubbing alcohol + q-tip) after every 10 hours of operation. I don’t think this unit has ever been cleaned. + +The belts might also need changing since they apparently go bad after a while. + +## Tapes catch on fire + +I read on the internet that peoples’ tapes have spontaneously combusted. This is a thing to avoid. diff --git a/content/posts/deploying-the-garden.md b/content/posts/deploying-the-garden.md deleted file mode 100644 index 105bfb7..0000000 --- a/content/posts/deploying-the-garden.md +++ /dev/null @@ -1,38 +0,0 @@ ---- -title: Deploying the garden -date: 2024-03-26 -extra: - kind: note ---- - -Every respectable tech blog must contain two posts: - -- ”This site now has RSS” -- ”This is how this site is deployed” - -Other content is optional. - -## Writing on the go - -Content updates go like this: - -1. I write Markdown in a special directory in my Obsidian Vault -2. The change is synced to my Digital Ocean server via Obsidian Sync (I run an Obsidian process on my server) -3. A file system monitor script picks it up and runs a build script -4. Built HTML files are served with Nginx - -This process gives me full control over the content wherever I have access to Obsidian. Making writing as easy as possible is the principle behind this design. - -[This is considered a good idea in the frog community.](https://www.todepond.com/wikiblogarden/art/never-stop-writing/on-your-phone) - -Making changes to the build process itself, or the HTML template, requires a computer, however. - -## Parentheses - -Most of the heavy lifting is done by `pandoc`. The glue parts use an obscure and ancient language with many curved sigils. Give it a try. - -{{ fig(src="/files/repo_langs.png", alt="A GitHub repository language breakdown showing that most of the code is written in Scheme.") }} - -## Hosting - -My small Digital Ocean server also moonlights as a container image registry. I build the scripts into an OCI image, push it on the server and run it with `docker-compose`. Don’t over-engineer when you want things _done_. diff --git a/content/posts/deploying-the-garden/index.md b/content/posts/deploying-the-garden/index.md new file mode 100644 index 0000000..cdb5caf --- /dev/null +++ b/content/posts/deploying-the-garden/index.md @@ -0,0 +1,38 @@ +--- +title: Deploying the garden +date: 2024-03-26 +extra: + kind: note +--- + +Every respectable tech blog must contain two posts: + +- ”This site now has RSS” +- ”This is how this site is deployed” + +Other content is optional. + +## Writing on the go + +Content updates go like this: + +1. I write Markdown in a special directory in my Obsidian Vault +2. The change is synced to my Digital Ocean server via Obsidian Sync (I run an Obsidian process on my server) +3. A file system monitor script picks it up and runs a build script +4. Built HTML files are served with Nginx + +This process gives me full control over the content wherever I have access to Obsidian. Making writing as easy as possible is the principle behind this design. + +[This is considered a good idea in the frog community.](https://www.todepond.com/wikiblogarden/art/never-stop-writing/on-your-phone) + +Making changes to the build process itself, or the HTML template, requires a computer, however. + +## Parentheses + +Most of the heavy lifting is done by `pandoc`. The glue parts use an obscure and ancient language with many curved sigils. Give it a try. + +{{ fig(src="repo_langs.png", alt="A GitHub repository language breakdown showing that most of the code is written in Scheme.") }} + +## Hosting + +My small Digital Ocean server also moonlights as a container image registry. I build the scripts into an OCI image, push it on the server and run it with `docker-compose`. Don’t over-engineer when you want things _done_. diff --git a/content/posts/deploying-the-garden/repo_langs.png b/content/posts/deploying-the-garden/repo_langs.png new file mode 100644 index 0000000..9c85b3b Binary files /dev/null and b/content/posts/deploying-the-garden/repo_langs.png differ diff --git a/content/posts/diddle-event-scheduler.md b/content/posts/diddle-event-scheduler.md deleted file mode 100644 index 701e252..0000000 --- a/content/posts/diddle-event-scheduler.md +++ /dev/null @@ -1,20 +0,0 @@ ---- -title: Diddle, a minimalist event scheduler -date: 2024-03-12 -extra: - kind: project ---- - -I don't like Doodle. It pushes ads and its premium subscription relentlessly while simultaneously feeling like a clumsy, lumbering beast. There is also a lot to be desired in terms of accessibility and mobile-friendliness. - -There are alternatives, like [dudle](https://dud-poll.inf.tu-dresden.de/) or [framadate](https://framadate.org/abc/en/). I could probably make do with those. However, to me this felt like an opportunity to spin up a quick MVP. This MVP turned into _diddle_. - -Diddle ([live instance](https://diddle.jan.systems/), [Github](https://github.com/jantuomi/diddle)) is a minimalist tool that focuses on fast load times, accessibility and pragmatism. The feature set is based on my own personal requirements. It works great for quickly filling out a poll on mobile. - -**Note**: The instance hosted on my domain might go down any second. Use it at your own discretion. - -{{ fig(src="/files/diddle_screenshot.png", alt="Diddle screenshot showing an answerable poll") }} - -The first 80% of the project was done in two evenings. The last 80% was done during the next week. The tech stack is the _get-shit-done_ stack: **Python** + **Flask** + **PostgreSQL**. There is no required client side javascript: all client side logic is strictly for [progressive enhancement](https://developer.mozilla.org/en-US/docs/Glossary/Progressive_Enhancement). You can use "dumb" browsers like w3m or lynx to enjoy Diddle just fine. - -Check out the README on Github for more info. diff --git a/content/posts/diddle-event-scheduler/diddle_screenshot.png b/content/posts/diddle-event-scheduler/diddle_screenshot.png new file mode 100644 index 0000000..437567e Binary files /dev/null and b/content/posts/diddle-event-scheduler/diddle_screenshot.png differ diff --git a/content/posts/diddle-event-scheduler/index.md b/content/posts/diddle-event-scheduler/index.md new file mode 100644 index 0000000..033a352 --- /dev/null +++ b/content/posts/diddle-event-scheduler/index.md @@ -0,0 +1,20 @@ +--- +title: Diddle, a minimalist event scheduler +date: 2024-03-12 +extra: + kind: project +--- + +I don't like Doodle. It pushes ads and its premium subscription relentlessly while simultaneously feeling like a clumsy, lumbering beast. There is also a lot to be desired in terms of accessibility and mobile-friendliness. + +There are alternatives, like [dudle](https://dud-poll.inf.tu-dresden.de/) or [framadate](https://framadate.org/abc/en/). I could probably make do with those. However, to me this felt like an opportunity to spin up a quick MVP. This MVP turned into _diddle_. + +Diddle ([live instance](https://diddle.jan.systems/), [Github](https://github.com/jantuomi/diddle)) is a minimalist tool that focuses on fast load times, accessibility and pragmatism. The feature set is based on my own personal requirements. It works great for quickly filling out a poll on mobile. + +**Note**: The instance hosted on my domain might go down any second. Use it at your own discretion. + +{{ fig(src="diddle_screenshot.png", alt="Diddle screenshot showing an answerable poll") }} + +The first 80% of the project was done in two evenings. The last 80% was done during the next week. The tech stack is the _get-shit-done_ stack: **Python** + **Flask** + **PostgreSQL**. There is no required client side javascript: all client side logic is strictly for [progressive enhancement](https://developer.mozilla.org/en-US/docs/Glossary/Progressive_Enhancement). You can use "dumb" browsers like w3m or lynx to enjoy Diddle just fine. + +Check out the README on Github for more info. diff --git a/content/posts/eurobsdcon-2025-conf-notes.md b/content/posts/eurobsdcon-2025-conf-notes.md deleted file mode 100644 index f10c781..0000000 --- a/content/posts/eurobsdcon-2025-conf-notes.md +++ /dev/null @@ -1,89 +0,0 @@ ---- -title: "EuroBSDCon 2025: conference notes" -date: 2025-10-17 -extra: - kind: note ---- - -EuroBSDCon is one of the events organized under the BSD community umbrella, focusing on all things related to and descending from 4.4 Berkeley Software Distribution. This year's event was held between September 25th and 28th in Zagreb, Croatia, but the city and country change every year. - -My event experience consisted of two days of "trainings", sometimes also referred to as labs, and two days of conference proper. In addition to these, there were also dev summits and smaller "conference-in-a-conference" kind of deals related to some BSD subsystems like `bhyve`, but those were not relevant to me so I skipped them outright. - -The event was held at the Faculty of Electrical Engineering and Computing (University of Zagreb). The venue was nostalgic in a way since it reminded me a lot of the building where I started my studies in Aalto University. Functional. - -{{ fig(src="/files/eurobsdcon_2025_hallway.jpeg", alt="A photo of a stairway in the conference venue") }} - -Recordings from the presentation rooms can be found on [Youtube](https://www.youtube.com/playlist?list=PLskKNopggjc6bnhXOX5MQbaPKEvohzjYH). You might have to check the talk schedule to find the timestamp of a given talk. - -## Kernel deep-dive - -I attended a two day lab by Prof. Kirk McKusick, who is a well known figure in the BSD world, having worked on core operating system components in the formative years of the BSD lineage. I really enjoyed his relaxed but still intensive teaching style. The lab, or maybe _interactive lecture_ would be more fitting, was an introduction to the FreeBSD kernel subsystems, explaining how things like I/O, scheduling, filesystems and networking work in the kernel. - -> Note that the FreeBSD kernel is not the same as the Linux kernel, or the other \*BSD kernels either for that matter. It is FreeBSD exclusive, but it shares history with other systems that descend from 4.4BSD, which is the common ancestor for many Unix-like OSs. - -You see, I'm not a huge kernel guy (at least not yet) so I was a bit worried that the lab content would go over my head. What happened though surprised me: I understood something like 90% of the material. Apparently I had the necessary prerequisite knowledge after all, or maybe Kirk was just so good at teaching. Maybe both. - -{{ fig(src="/files/eurobsdcon_2025_binders.jpeg", alt="Two spiral notebooks of lecture notes, one for each day") }} - -## Favorite talks - -There was a healthy range of themes in the talk schedule. Some were straightforward demos of recent development efforts, some were about history (mostly 1970–80s, the childhood years of the BSD project). Some war stories as well, explaining how the speaker et al. dealt with some hindrance. There were also some, I would say _not so BSD-related_, talks about things like data and identity ownership, federated social media, freedom. There is a bunch of overlap between BSD enthusiasts and digital freedom enthusiasts so it was only fitting. - -Here's my top 3, based on overall vibes. - -### _Lessons learned Open Sourcing the UK's Covid Tracing App_, by Terence Eden - -Terence was the third in line in the Saturday's opening keynote schedule. Terence was one of the key engineers behind the open source Covid tracing app that the UK developed and used. The keynote handled themes of public relations, talking about software licenses to lawyers, protecting privacy of developers from the wrath of the public, successful platform launches and shutdowns... a lot of stuff delivered with great pacing and enthusiasm. - -Timestamped link to this talk: [Youtube](https://www.youtube.com/live/qdEMmM5g27M?t=1221) - -### _Enhancing Unix Education through Chaos Engineering and Gamification using FreeBSD_, by Benedict Reuschling, Andreas Kirschner - -Benedict and Andreas had noticed that their students at Hochschule Darmstadt were not very proficient when it came to administering a UNIX system and debugging issues on it. They devised a platform where their students use exercise environments (with root access) and solve problems like DNS problems against the clock. The fastest student teams get winning points on a leaderboard. - -The talk format was a mix of a war story and tech demo: they explained their pedagogical conundrum and walked the viewers through their solution, which was based on FreeBSD and liberal use of jails. - -Timestamped link to this talk: [Youtube](https://www.youtube.com/live/FgtTVzYFEF0?t=23860) - -### _AI slop attacks on the curl project_, by Daniel Stenberg - -Daniel is the main developer in the `curl` project. In his Sunday keynote he explained how the project is being pestered by AI generated, low effort security issue reports that take up a lot of contributor time to review. Why not just block those people outright? Daniel argues that they take all reports indicating a critical vulnerability seriously; they can not allow to mistakenly disregard a valid report. - -Daniel is an entertaining speaker with big responsibility in the open source world. - -Sadly, the talk seems to not be recorded on Youtube. - -## Community - -I was curious to see what the people are like. I knew that BSD is a pretty niche thing so I was afraid the community might be hard to get into, but I was happy to notice that everyone that I talked to seemed very welcoming and excited to share. - -I noticed that there was very little "hierarchy" at the event. Speakers, sponsors and attendees were all mingling together at lunches and at social events. I didn't really see signs of cliques or circles that didn't accommodate newcomers. - -Since the event, I have familiarised myself with BSD community channels such as the [FreeBSD IRC channels](https://wiki.freebsd.org/IRC/Channels) on Libera.Chat and the various forums of [BSD Cafe](https://wiki.bsd.cafe/), hosted by Stefano Marinelli, who was also speaking at the conference. - -{{ fig(src="/files/eurobsdcon_2025_group.jpeg", alt="A group photo was taken on the final day") }} - -## There and back again, and the touristy bits - -There were no direct flights from Helsinki to Zagreb, so I had to layover in Germany both on the way there and on the way back. Leaving Finland, the departure time was gnarly: around five in the morning. Good thing I can sleep in economy. I had plenty of time to change planes, and got to the destination without a hitch. - -I stayed in B&B Boutique Casablanca, which was a nice bed & breakfast place with friendly staff. - -Traditional & local foods were my primary target when looking for restaurants. My top picks: - -- **ćevapi** at _Plac_ ([Tripadvisor](https://www.tripadvisor.com/Restaurant_Review-g294454-d7075337-Reviews-Plac-Zagreb_Central_Croatia.html)), pictured below -- **štrukli** at _La Štruk_ ([Website](https://www.lastruk.com/)) - -{{ fig(src="/files/eurobsdcon_2025_cevapi.jpeg", alt="A serving of ćevapi with cream cheese and paprika spread") }} - -The return trip was more eventful. The plane didn't start boarding until it was more than an hour late. We were never told the reason. That delay caused me to miss my connecting flight 😑 - -I was rerouted via Frankfurt which turned my nice "get home for dinner" schedule into a gruesome "you're home sometime between midnight and 2 AM" trek. Luckily I got a seat on both of the rerouted flights (that was not a given). I was home after 1 AM. - -I have then learned that there would have been a very nice direct route from Finland to Ljubljana, which lies just across the Croatian border, accompanied by a bus route that would have taken me from city to city in a couple of hours. Now I know. - -## The next event - -...will be held in Brussels, Belgium. It's closer to Finland (👍) but I've heard that it's kind of a dull city to visit. Whether that's true or not, I don't know really, never been there before. But I'd prefer going a bit farther away from my home to escape the weather 😅 - -[EuroBSDCon 2026 website](https://2026.eurobsdcon.org/) diff --git a/content/posts/eurobsdcon-2025-conf-notes/eurobsdcon_2025_binders.jpeg b/content/posts/eurobsdcon-2025-conf-notes/eurobsdcon_2025_binders.jpeg new file mode 100644 index 0000000..ee02077 Binary files /dev/null and b/content/posts/eurobsdcon-2025-conf-notes/eurobsdcon_2025_binders.jpeg differ diff --git a/content/posts/eurobsdcon-2025-conf-notes/eurobsdcon_2025_cevapi.jpeg b/content/posts/eurobsdcon-2025-conf-notes/eurobsdcon_2025_cevapi.jpeg new file mode 100644 index 0000000..67595b4 Binary files /dev/null and b/content/posts/eurobsdcon-2025-conf-notes/eurobsdcon_2025_cevapi.jpeg differ diff --git a/content/posts/eurobsdcon-2025-conf-notes/eurobsdcon_2025_group.jpeg b/content/posts/eurobsdcon-2025-conf-notes/eurobsdcon_2025_group.jpeg new file mode 100644 index 0000000..b94c739 Binary files /dev/null and b/content/posts/eurobsdcon-2025-conf-notes/eurobsdcon_2025_group.jpeg differ diff --git a/content/posts/eurobsdcon-2025-conf-notes/eurobsdcon_2025_hallway.jpeg b/content/posts/eurobsdcon-2025-conf-notes/eurobsdcon_2025_hallway.jpeg new file mode 100644 index 0000000..f15217a Binary files /dev/null and b/content/posts/eurobsdcon-2025-conf-notes/eurobsdcon_2025_hallway.jpeg differ diff --git a/content/posts/eurobsdcon-2025-conf-notes/index.md b/content/posts/eurobsdcon-2025-conf-notes/index.md new file mode 100644 index 0000000..33a0f7d --- /dev/null +++ b/content/posts/eurobsdcon-2025-conf-notes/index.md @@ -0,0 +1,89 @@ +--- +title: "EuroBSDCon 2025: conference notes" +date: 2025-10-17 +extra: + kind: note +--- + +EuroBSDCon is one of the events organized under the BSD community umbrella, focusing on all things related to and descending from 4.4 Berkeley Software Distribution. This year's event was held between September 25th and 28th in Zagreb, Croatia, but the city and country change every year. + +My event experience consisted of two days of "trainings", sometimes also referred to as labs, and two days of conference proper. In addition to these, there were also dev summits and smaller "conference-in-a-conference" kind of deals related to some BSD subsystems like `bhyve`, but those were not relevant to me so I skipped them outright. + +The event was held at the Faculty of Electrical Engineering and Computing (University of Zagreb). The venue was nostalgic in a way since it reminded me a lot of the building where I started my studies in Aalto University. Functional. + +{{ fig(src="eurobsdcon_2025_hallway.jpeg", alt="A photo of a stairway in the conference venue") }} + +Recordings from the presentation rooms can be found on [Youtube](https://www.youtube.com/playlist?list=PLskKNopggjc6bnhXOX5MQbaPKEvohzjYH). You might have to check the talk schedule to find the timestamp of a given talk. + +## Kernel deep-dive + +I attended a two day lab by Prof. Kirk McKusick, who is a well known figure in the BSD world, having worked on core operating system components in the formative years of the BSD lineage. I really enjoyed his relaxed but still intensive teaching style. The lab, or maybe _interactive lecture_ would be more fitting, was an introduction to the FreeBSD kernel subsystems, explaining how things like I/O, scheduling, filesystems and networking work in the kernel. + +> Note that the FreeBSD kernel is not the same as the Linux kernel, or the other \*BSD kernels either for that matter. It is FreeBSD exclusive, but it shares history with other systems that descend from 4.4BSD, which is the common ancestor for many Unix-like OSs. + +You see, I'm not a huge kernel guy (at least not yet) so I was a bit worried that the lab content would go over my head. What happened though surprised me: I understood something like 90% of the material. Apparently I had the necessary prerequisite knowledge after all, or maybe Kirk was just so good at teaching. Maybe both. + +{{ fig(src="eurobsdcon_2025_binders.jpeg", alt="Two spiral notebooks of lecture notes, one for each day") }} + +## Favorite talks + +There was a healthy range of themes in the talk schedule. Some were straightforward demos of recent development efforts, some were about history (mostly 1970–80s, the childhood years of the BSD project). Some war stories as well, explaining how the speaker et al. dealt with some hindrance. There were also some, I would say _not so BSD-related_, talks about things like data and identity ownership, federated social media, freedom. There is a bunch of overlap between BSD enthusiasts and digital freedom enthusiasts so it was only fitting. + +Here's my top 3, based on overall vibes. + +### _Lessons learned Open Sourcing the UK's Covid Tracing App_, by Terence Eden + +Terence was the third in line in the Saturday's opening keynote schedule. Terence was one of the key engineers behind the open source Covid tracing app that the UK developed and used. The keynote handled themes of public relations, talking about software licenses to lawyers, protecting privacy of developers from the wrath of the public, successful platform launches and shutdowns... a lot of stuff delivered with great pacing and enthusiasm. + +Timestamped link to this talk: [Youtube](https://www.youtube.com/live/qdEMmM5g27M?t=1221) + +### _Enhancing Unix Education through Chaos Engineering and Gamification using FreeBSD_, by Benedict Reuschling, Andreas Kirschner + +Benedict and Andreas had noticed that their students at Hochschule Darmstadt were not very proficient when it came to administering a UNIX system and debugging issues on it. They devised a platform where their students use exercise environments (with root access) and solve problems like DNS problems against the clock. The fastest student teams get winning points on a leaderboard. + +The talk format was a mix of a war story and tech demo: they explained their pedagogical conundrum and walked the viewers through their solution, which was based on FreeBSD and liberal use of jails. + +Timestamped link to this talk: [Youtube](https://www.youtube.com/live/FgtTVzYFEF0?t=23860) + +### _AI slop attacks on the curl project_, by Daniel Stenberg + +Daniel is the main developer in the `curl` project. In his Sunday keynote he explained how the project is being pestered by AI generated, low effort security issue reports that take up a lot of contributor time to review. Why not just block those people outright? Daniel argues that they take all reports indicating a critical vulnerability seriously; they can not allow to mistakenly disregard a valid report. + +Daniel is an entertaining speaker with big responsibility in the open source world. + +Sadly, the talk seems to not be recorded on Youtube. + +## Community + +I was curious to see what the people are like. I knew that BSD is a pretty niche thing so I was afraid the community might be hard to get into, but I was happy to notice that everyone that I talked to seemed very welcoming and excited to share. + +I noticed that there was very little "hierarchy" at the event. Speakers, sponsors and attendees were all mingling together at lunches and at social events. I didn't really see signs of cliques or circles that didn't accommodate newcomers. + +Since the event, I have familiarised myself with BSD community channels such as the [FreeBSD IRC channels](https://wiki.freebsd.org/IRC/Channels) on Libera.Chat and the various forums of [BSD Cafe](https://wiki.bsd.cafe/), hosted by Stefano Marinelli, who was also speaking at the conference. + +{{ fig(src="eurobsdcon_2025_group.jpeg", alt="A group photo was taken on the final day") }} + +## There and back again, and the touristy bits + +There were no direct flights from Helsinki to Zagreb, so I had to layover in Germany both on the way there and on the way back. Leaving Finland, the departure time was gnarly: around five in the morning. Good thing I can sleep in economy. I had plenty of time to change planes, and got to the destination without a hitch. + +I stayed in B&B Boutique Casablanca, which was a nice bed & breakfast place with friendly staff. + +Traditional & local foods were my primary target when looking for restaurants. My top picks: + +- **ćevapi** at _Plac_ ([Tripadvisor](https://www.tripadvisor.com/Restaurant_Review-g294454-d7075337-Reviews-Plac-Zagreb_Central_Croatia.html)), pictured below +- **štrukli** at _La Štruk_ ([Website](https://www.lastruk.com/)) + +{{ fig(src="eurobsdcon_2025_cevapi.jpeg", alt="A serving of ćevapi with cream cheese and paprika spread") }} + +The return trip was more eventful. The plane didn't start boarding until it was more than an hour late. We were never told the reason. That delay caused me to miss my connecting flight 😑 + +I was rerouted via Frankfurt which turned my nice "get home for dinner" schedule into a gruesome "you're home sometime between midnight and 2 AM" trek. Luckily I got a seat on both of the rerouted flights (that was not a given). I was home after 1 AM. + +I have then learned that there would have been a very nice direct route from Finland to Ljubljana, which lies just across the Croatian border, accompanied by a bus route that would have taken me from city to city in a couple of hours. Now I know. + +## The next event + +...will be held in Brussels, Belgium. It's closer to Finland (👍) but I've heard that it's kind of a dull city to visit. Whether that's true or not, I don't know really, never been there before. But I'd prefer going a bit farther away from my home to escape the weather 😅 + +[EuroBSDCon 2026 website](https://2026.eurobsdcon.org/) diff --git a/content/posts/home-server-part-1.md b/content/posts/home-server-part-1.md deleted file mode 100644 index 28bfbb4..0000000 --- a/content/posts/home-server-part-1.md +++ /dev/null @@ -1,78 +0,0 @@ ---- -title: "A home server journey, part 1: Motivation" -date: 2025-06-30 -description: | - This is the first post in a blog series chronicling my progress with designing and setting up a new, physical FreeBSD home server. -extra: - kind: note ---- - -This is the first post in a blog series chronicling my progress with designing and setting up a new, physical FreeBSD home server. I'm guessing this will become at least a three-parter, but that depends on how things actually go, and how in-depth I happen to write. This first part is about my motivations and requirements, whereas the latter ones will be more technical-deepdive-like. - -{{ toc() }} - -{{ fig(src="/files/aliexpress_minipc.png", alt="A promotional image portraying the mini-PC I ended up getting") }} - -## I need a place to run code and store files - -I have pretty simple requirements for a server. I need to run a bunch of services, most of which are Node.js or Python apps, and serve some static websites and files. These are primarily personal applications: tracking finances, or keeping track of house chores, but also hobby projects that I host and expose to the world. - -I also need to store files somewhere, like everyone that partakes in the modern digital society. There isn't that much data to store, really. I don't take that many photos or videos, which tend to be the main driver for purchasing more cloud storage, but I have some Ableton project files (music production) that take up some space. Currently all my project files fit in a couple dozen gigabytes. I store them alongside my documents in Google Drive, using a real-time Drive sync app. This does not include the audio samples and recordings in my projects: I would have to buy a restrictively expensive plan to store those in Drive. - -I have a trusty Ubuntu server, chugging away in some rack in a DigitalOcean datacenter in Amsterdam. Even though DigitalOcean pricing is pretty reasonable for individuals in general, my monthly price tag is around ~35€, after taxes. That sum is based on the cost of running a medium sized x86-64 instance and storing some backup snapshots from that machine. Times twelve, that comes to around 420€ per year. To me, that is a sizeable amount of money. - -{{ fig(src="/files/do-june-billing.png", alt="DigitalOcean is charging $36.14 this month") }} - -> 🤔 I would like to run a smaller instance, but I needed a certain level of RAM to reliably run Obsidian Sync. For whatever reason, less memory would have the app crash intermittently. - -Having upgraded to instance to the current size, it is hard to downscale back to a smaller machine. It is possible, of course. But instead of doing that, I started to consider an alternative: maybe I could save some coins and get some hardware of my own, like in the prehistoric times, before cloud services became the de-facto way to run anything. With one year's cloud costs, I could surely get a capable small machine. - -{{ fig(src="/files/old-man-yells-at-cloud.png", alt="Old man yells at Cloud (FFVII)") }} - -## Ownership and control - -Money is not the only motivator. I dislike the idea of having the continuity of my digital presence rely on American / multinational corporations that have no real responsibility when it comes to ensuring my files stay intact, containers running and website up. Google could ban my account without batting an eyelid, which would directly make me lose access to my collection of personal photos, important documents, and various other files I prefer not lose. DigitalOcean could flag my server for suspicious activity. - -All this isn't very likely, but it still happens. And even if it didn't, I don't like having a soulless, corporate entity being the arbitrator on whether I can access my own digital life or not. I would prefer to have the probability of an account-frozen catastrophe be exactly zero instead of 0.1%. - -> 🤔 In terms of cloud provider trustworthiness, I think DigitalOcean is pretty high up in the rankings. I haven't seen much ire against them.🤔 - -I could provide these capabilities to myself, by managing my own hardware and networking. I could set up a NAS (network attached storage) box for the files, and run the services locally on either the same machine or some other hardware. It would have to be publicly accessible (Internet), just like my DigitalOcean VPS. It would have to provide _at least_ the same guarantees as the status quo: periodic restorable backups, redundant storage and at-rest encryption for stored files. - -> As an alternative, I could move my stuff to a European, maybe even Finnish, company. There are some [shared hosting](https://en.wikipedia.org/wiki/Shared_web_hosting_service) providers that exist. I don't really consider this to be that good of an option. I feel that if I'm moving out of the public cloud, I might as well go all the way. - -To replace something as generally trusted and widely used as Google Drive requires some proper thinking. I haven't previously set up any redundant disk storage. My systems have never followed the [3-2-1 backup rule](https://en.wikipedia.org/wiki/Backup#3-2-1_Backup_Rule). - -{{ fig(src="/files/3-2-1-Backup-Rule.png", alt="Maintain at least 3 copies of your data, keep 2 copies stored at separate locations, store at least 1 copy at an off-site location. Source: msp360.com") }} - -Reaching for full ownership and control is not solely an objective aim. It is in part psychological, an attempt to get back control in a world of weakening privacy and walled content gardens. It is driven by emotion. And this is completely fine: humans are allowed to be motivated by emotion, especially when it comes to personal, hobby-adjacent things. To claim otherwise would be dishonest. - -## Physical electronics are fun, tinkering is fun - -There is an inherent value in physical things. People collect music on CDs, cassette tapes, and vinyls. Playing games on real hardware is seen differently from emulation. A computer running in my home is "more real" than a resource-sharing virtual machine in some datacenter. - -It is nice to own, instead of rent. It is nice to not be bound by a contract, or a service agreement. Having fewer dependencies, legal or technical, is often favorable. - -I can pick out the components and design the infrastructure for my system. I know the moving parts, I know that if something fails, one of the cogs in the machine has stopped turning. I then replace the cog. There is no mystery, at least in an [unknown-unknowns](https://en.wikipedia.org/wiki/There_are_unknown_unknowns) kind of way. - -As long as things are not that serious, maintaining and administrating physical systems can be pretty fun. - -## Simplicity begets reliability - -Physical components are one thing, software is another. I have been running Ubuntu on the VPS for a long time, but I can think of multiple reasons to reconsider that choice. - -When I picked Ubuntu years ago, I didn't know what kind of programs I would be running on the machine. I opted for a Linux distribution that is well supported and offers relatively recent versions of software. I was also uncertain about whether or not I would need to run graphical software. Ubuntu ships with `snap` and `flatpak` , both of which are used to run desktop apps. It is now clear to me that I don't need any of this; stable server software is all I need. - -I am inspired by simplicity. I feel that systems are often improved by removing, not adding. - -I don't understand what is going on in a Ubuntu installation. There are so many components that interact, it's hard to make sense of what affects what. There are sediment layers of Linux history in there, with some Canonical-added goodness in between. To be fair, I don't think I understand the inner workings of any Linux distribution, for that matter. - -A friend had recently set up a bunch of machines using FreeBSD. The elegance of simplicity lures me in. FreeBSD is well respected when it comes to running stable server software. The system boots up with just a couple of processes running in `top`. The fantastic [FreeBSD handbook](https://docs.freebsd.org/en/books/handbook/) and supplementary reading by [Michael W. Lucas](https://mwl.io/nonfiction/os) directly teach what each file under `/etc/` does. - -I did some more reading as well as some experimentation with a FreeBSD virtual machine. It quickly became clear that this should be the way forward. I am excited to try out new, but at the same time respected and old, technology. - -FreeBSD is different. A familiar, but still strange system is fascinating. I have only worked with Linux systems before, so many things are as I expect them to be, while similarly many surprise me. - -## To be continued - -In the next part we'll dive into technical good stuff: setting up the machine and installing FreeBSD. diff --git a/content/posts/home-server-part-1/3-2-1-Backup-Rule.png b/content/posts/home-server-part-1/3-2-1-Backup-Rule.png new file mode 100644 index 0000000..ed6b675 Binary files /dev/null and b/content/posts/home-server-part-1/3-2-1-Backup-Rule.png differ diff --git a/content/posts/home-server-part-1/aliexpress_minipc.png b/content/posts/home-server-part-1/aliexpress_minipc.png new file mode 100644 index 0000000..0cb57a4 Binary files /dev/null and b/content/posts/home-server-part-1/aliexpress_minipc.png differ diff --git a/content/posts/home-server-part-1/do-june-billing.png b/content/posts/home-server-part-1/do-june-billing.png new file mode 100644 index 0000000..a38f71c Binary files /dev/null and b/content/posts/home-server-part-1/do-june-billing.png differ diff --git a/content/posts/home-server-part-1/index.md b/content/posts/home-server-part-1/index.md new file mode 100644 index 0000000..6d859cc --- /dev/null +++ b/content/posts/home-server-part-1/index.md @@ -0,0 +1,78 @@ +--- +title: "A home server journey, part 1: Motivation" +date: 2025-06-30 +description: | + This is the first post in a blog series chronicling my progress with designing and setting up a new, physical FreeBSD home server. +extra: + kind: note +--- + +This is the first post in a blog series chronicling my progress with designing and setting up a new, physical FreeBSD home server. I'm guessing this will become at least a three-parter, but that depends on how things actually go, and how in-depth I happen to write. This first part is about my motivations and requirements, whereas the latter ones will be more technical-deepdive-like. + +{{ toc() }} + +{{ fig(src="aliexpress_minipc.png", alt="A promotional image portraying the mini-PC I ended up getting") }} + +## I need a place to run code and store files + +I have pretty simple requirements for a server. I need to run a bunch of services, most of which are Node.js or Python apps, and serve some static websites and files. These are primarily personal applications: tracking finances, or keeping track of house chores, but also hobby projects that I host and expose to the world. + +I also need to store files somewhere, like everyone that partakes in the modern digital society. There isn't that much data to store, really. I don't take that many photos or videos, which tend to be the main driver for purchasing more cloud storage, but I have some Ableton project files (music production) that take up some space. Currently all my project files fit in a couple dozen gigabytes. I store them alongside my documents in Google Drive, using a real-time Drive sync app. This does not include the audio samples and recordings in my projects: I would have to buy a restrictively expensive plan to store those in Drive. + +I have a trusty Ubuntu server, chugging away in some rack in a DigitalOcean datacenter in Amsterdam. Even though DigitalOcean pricing is pretty reasonable for individuals in general, my monthly price tag is around ~35€, after taxes. That sum is based on the cost of running a medium sized x86-64 instance and storing some backup snapshots from that machine. Times twelve, that comes to around 420€ per year. To me, that is a sizeable amount of money. + +{{ fig(src="do-june-billing.png", alt="DigitalOcean is charging $36.14 this month") }} + +> 🤔 I would like to run a smaller instance, but I needed a certain level of RAM to reliably run Obsidian Sync. For whatever reason, less memory would have the app crash intermittently. + +Having upgraded to instance to the current size, it is hard to downscale back to a smaller machine. It is possible, of course. But instead of doing that, I started to consider an alternative: maybe I could save some coins and get some hardware of my own, like in the prehistoric times, before cloud services became the de-facto way to run anything. With one year's cloud costs, I could surely get a capable small machine. + +{{ fig(src="old-man-yells-at-cloud.png", alt="Old man yells at Cloud (FFVII)") }} + +## Ownership and control + +Money is not the only motivator. I dislike the idea of having the continuity of my digital presence rely on American / multinational corporations that have no real responsibility when it comes to ensuring my files stay intact, containers running and website up. Google could ban my account without batting an eyelid, which would directly make me lose access to my collection of personal photos, important documents, and various other files I prefer not lose. DigitalOcean could flag my server for suspicious activity. + +All this isn't very likely, but it still happens. And even if it didn't, I don't like having a soulless, corporate entity being the arbitrator on whether I can access my own digital life or not. I would prefer to have the probability of an account-frozen catastrophe be exactly zero instead of 0.1%. + +> 🤔 In terms of cloud provider trustworthiness, I think DigitalOcean is pretty high up in the rankings. I haven't seen much ire against them.🤔 + +I could provide these capabilities to myself, by managing my own hardware and networking. I could set up a NAS (network attached storage) box for the files, and run the services locally on either the same machine or some other hardware. It would have to be publicly accessible (Internet), just like my DigitalOcean VPS. It would have to provide _at least_ the same guarantees as the status quo: periodic restorable backups, redundant storage and at-rest encryption for stored files. + +> As an alternative, I could move my stuff to a European, maybe even Finnish, company. There are some [shared hosting](https://en.wikipedia.org/wiki/Shared_web_hosting_service) providers that exist. I don't really consider this to be that good of an option. I feel that if I'm moving out of the public cloud, I might as well go all the way. + +To replace something as generally trusted and widely used as Google Drive requires some proper thinking. I haven't previously set up any redundant disk storage. My systems have never followed the [3-2-1 backup rule](https://en.wikipedia.org/wiki/Backup#3-2-1_Backup_Rule). + +{{ fig(src="3-2-1-Backup-Rule.png", alt="Maintain at least 3 copies of your data, keep 2 copies stored at separate locations, store at least 1 copy at an off-site location. Source: msp360.com") }} + +Reaching for full ownership and control is not solely an objective aim. It is in part psychological, an attempt to get back control in a world of weakening privacy and walled content gardens. It is driven by emotion. And this is completely fine: humans are allowed to be motivated by emotion, especially when it comes to personal, hobby-adjacent things. To claim otherwise would be dishonest. + +## Physical electronics are fun, tinkering is fun + +There is an inherent value in physical things. People collect music on CDs, cassette tapes, and vinyls. Playing games on real hardware is seen differently from emulation. A computer running in my home is "more real" than a resource-sharing virtual machine in some datacenter. + +It is nice to own, instead of rent. It is nice to not be bound by a contract, or a service agreement. Having fewer dependencies, legal or technical, is often favorable. + +I can pick out the components and design the infrastructure for my system. I know the moving parts, I know that if something fails, one of the cogs in the machine has stopped turning. I then replace the cog. There is no mystery, at least in an [unknown-unknowns](https://en.wikipedia.org/wiki/There_are_unknown_unknowns) kind of way. + +As long as things are not that serious, maintaining and administrating physical systems can be pretty fun. + +## Simplicity begets reliability + +Physical components are one thing, software is another. I have been running Ubuntu on the VPS for a long time, but I can think of multiple reasons to reconsider that choice. + +When I picked Ubuntu years ago, I didn't know what kind of programs I would be running on the machine. I opted for a Linux distribution that is well supported and offers relatively recent versions of software. I was also uncertain about whether or not I would need to run graphical software. Ubuntu ships with `snap` and `flatpak` , both of which are used to run desktop apps. It is now clear to me that I don't need any of this; stable server software is all I need. + +I am inspired by simplicity. I feel that systems are often improved by removing, not adding. + +I don't understand what is going on in a Ubuntu installation. There are so many components that interact, it's hard to make sense of what affects what. There are sediment layers of Linux history in there, with some Canonical-added goodness in between. To be fair, I don't think I understand the inner workings of any Linux distribution, for that matter. + +A friend had recently set up a bunch of machines using FreeBSD. The elegance of simplicity lures me in. FreeBSD is well respected when it comes to running stable server software. The system boots up with just a couple of processes running in `top`. The fantastic [FreeBSD handbook](https://docs.freebsd.org/en/books/handbook/) and supplementary reading by [Michael W. Lucas](https://mwl.io/nonfiction/os) directly teach what each file under `/etc/` does. + +I did some more reading as well as some experimentation with a FreeBSD virtual machine. It quickly became clear that this should be the way forward. I am excited to try out new, but at the same time respected and old, technology. + +FreeBSD is different. A familiar, but still strange system is fascinating. I have only worked with Linux systems before, so many things are as I expect them to be, while similarly many surprise me. + +## To be continued + +In the next part we'll dive into technical good stuff: setting up the machine and installing FreeBSD. diff --git a/content/posts/home-server-part-1/old-man-yells-at-cloud.png b/content/posts/home-server-part-1/old-man-yells-at-cloud.png new file mode 100644 index 0000000..bf6a0f3 Binary files /dev/null and b/content/posts/home-server-part-1/old-man-yells-at-cloud.png differ diff --git a/content/posts/home-server-part-2.md b/content/posts/home-server-part-2.md deleted file mode 100644 index 3030d62..0000000 --- a/content/posts/home-server-part-2.md +++ /dev/null @@ -1,91 +0,0 @@ ---- -title: "A home server journey, part 2: Hardware" -date: 2025-10-04 -description: | - This is the second post in a blog series chronicling my progress with designing and setting up a new, physical FreeBSD home server. -extra: - kind: note ---- - -
“My first impulse, when presented with any spanking-new piece of computer hardware, is to imagine how it will look in ten years’ time, gathering dust under a card table in a thrift shop.” -― William Gibson, Distrust That Particular Flavor -
-
- -This is the second post in a blog series chronicling my progress with designing and setting up a new, physical FreeBSD home server. Last time we went over my motivations, and this time we're looking at the physical hardware. - -Check out episode [1](/posts/home-server-part-1). - -{{ toc() }} - -## Budget specs - -My yearly cost in the public cloud is around 420€. It feels reasonable for a project of this caliber that a year’s expenses could buy a capable minicomputer. - -What is a capable minicomputer then, you may ask. Well, my needs are pretty straightforward: - - - - - - - - - - - - - -
Qualitative requirements
UsefulnessCan run a range of small to medium load web services without a hitch
TrustworthinessData is durable, disk failures are not catastrophic, I can sleep at night
ExpandabilityCan add more terabytes in the future
Quantitative requirements
CPULow power, economical, can run FreeBSD.
MemoryWon’t OOM swap in day-to-day operation, ZFS is happy
Disk I/OI don’t really care, no high-throughput required. As long as it’s not especially bad (i.e. HDD-level bad).
Disk spaceSlots for multiple SSDs (RAID). Enough space to hold important files and some media with redundancy. No need for data hoarding.
Network2 interfaces (private and public networks), Gigabit ethernet. No need for wireless.
AnnoyanceLow noise, low temps, sleek form factor
- -With this list of requirements, I handwaved some models and numbers: - -- CPU: Intel N series ⭐️ -- Memory: 16GB -- Disk space: 2TB (usable) - -> ⭐️ Intel N series processors, formally known as [Alder Lake-N](https://en.wikipedia.org/wiki/Alder_Lake#Alder_Lake-N) and [Twin Lake-N](https://en.wikipedia.org/wiki/Alder_Lake#Twin_lake-N), are affordable processors designed to be primarily used in mobile devices, lower end laptops (think Chromebooks) and small workstations. Some models, such as the N100, N150, N200 and N250, have thermal power ratings as low as 6W, making them popular in the passively-cooled, quasi-embedded design space: routers, NAS's, and the like. - -I got the idea for an Intel N series chip from a friend who has succesfully set up computing cluster from a bunch of small machines with those chips. They seemed to be just what I was looking for. - -With these, I sail off towards the east (AliExpress). - -By the way, I also looked at ARM and RISC-V chips for this. I figured that if Apple Silicon can work as well as it does while being based on ARM, what's stopping me from reaping the same benefits, at least in part? ARM has T1 support from FreeBSD. - -Turns out that there simply aren't any reasonably priced machines based on chips comparable to e.g. the Intel N150. Everything's either designed for mobile devices or just prohibitively expensive. Maybe there is no market yet for small non-x64 computers? - -## Sourcing the silicon - -AliExpress, an online retailer based in China, is nice since they have a huge variety of devices at prices that would never be profitable if sold in a western shop. I would prefer to buy European in this current political atmosphere, but I'm working off a budget, and limiting my options to reputable and ethical vendors would force me to relax my requirements a lot or stop the project in its tracks altogether. - -{{ fig(src="/files/aliexpress_minipc_results.png", alt='Some of the results of a "mini pc" search query') }} - -I'm happy to notice that there is a varied selection of devices at the ~200€ price point. Some are designed to be network devices, with a bunch of physical interfaces and limited storage options, while some are clearly NAS oriented, with SATA 3.5" drive bays capable of housing two or four disks, most commonly. - -I'm looking for something in the middle ground: I need two physical interfaces (a property of router-likes) but I also need storage, preferably of the solid state variety (a property of NAS-likes). - -**Conclusion**: I'm going with this one (possibly stale link warning): [Topton Pocket Mini PC](https://www.aliexpress.com/item/1005007905020917.html). With some mystery promotions applied, I had to only pay 162.27€, with free shipping. I also purchased 16GB of RAM (Crucial DDR5 SODIMM 4800MHz) separately, also from AliExpress. - -> AliExpress tends to have multiple layers of ‼️ LIMITED SALE ‼️ and 🤑 TOP DEAL COUPON 🤑 going on at the same time. In the mobile app they also have a bunch of connect-four-type games that can score you big AliExpress Coins©. Please continue consuming. - -{{ fig(src="/files/aliexpress_minipc.png", alt="A promotional image of the mini PC I picked, hosted here in case the product page goes poof. Please enjoy the broken English.") }} - -Regarding disks, I think I want to buy them locally, just in case there are issues with them. It's way less stressful to handle returns and such with a local vendor than with an overseas megacorp. I feel that stuff like RAM is pretty often OK even if bought from less reputable sources, but with disks I don't want to test my luck. - -**Conclusion**: I'm going to get 4 x 1 TB (double the required usable space because of mirroring) of basic NVMe drives from a computer parts shop (Gigantti) nearby. - -## Setting up hardware - -The box arrives after a bit of a wait (par for the course for the cheapest delivery option, China Airmail). Everything seems to be in order content-wise, but there is no installation manual whatsoever included. I would like one, since I need to figure out how to install the RAM stick. It seems to somehow go below the NVMe sticks, but there is no obvious mechanism for detaching the NVMe bay. - -However, with a bit of trial and error I find the correct screw to remove. The top layer comes off, revealing the RAM stick slot underneath. Very carefully I slot in the memory and push down the clamp that fixes it in place. - -With similar care I put the drive bay back in, and connect the four NVMe drives and stick on their respective thermal material stickers that conduct heat for the drives to the heatsink on top of the machine. I screw everything in place with the tiny screws that came with the machine. A magnetic screwdriver would be a godsend now! - -After a bit, everything's put together. After connecting the power cable, the machine powers on without me even touching the power switch, which is a bit surprising, but ok. I hear the POST beep. - -Success! - -## To be continued - -In the next part(s) we will be looking at installing FreeBSD for the first (and second) time, and networking. I had some – let's say curious – issues with DHCP leases from the ISP. We might also take a look at my jail setup. diff --git a/content/posts/home-server-part-2/aliexpress_minipc.png b/content/posts/home-server-part-2/aliexpress_minipc.png new file mode 100644 index 0000000..0cb57a4 Binary files /dev/null and b/content/posts/home-server-part-2/aliexpress_minipc.png differ diff --git a/content/posts/home-server-part-2/aliexpress_minipc_results.png b/content/posts/home-server-part-2/aliexpress_minipc_results.png new file mode 100644 index 0000000..9242e3e Binary files /dev/null and b/content/posts/home-server-part-2/aliexpress_minipc_results.png differ diff --git a/content/posts/home-server-part-2/index.md b/content/posts/home-server-part-2/index.md new file mode 100644 index 0000000..7cd5300 --- /dev/null +++ b/content/posts/home-server-part-2/index.md @@ -0,0 +1,91 @@ +--- +title: "A home server journey, part 2: Hardware" +date: 2025-10-04 +description: | + This is the second post in a blog series chronicling my progress with designing and setting up a new, physical FreeBSD home server. +extra: + kind: note +--- + +
“My first impulse, when presented with any spanking-new piece of computer hardware, is to imagine how it will look in ten years’ time, gathering dust under a card table in a thrift shop.” +― William Gibson, Distrust That Particular Flavor +
+
+ +This is the second post in a blog series chronicling my progress with designing and setting up a new, physical FreeBSD home server. Last time we went over my motivations, and this time we're looking at the physical hardware. + +Check out episode [1](/posts/home-server-part-1). + +{{ toc() }} + +## Budget specs + +My yearly cost in the public cloud is around 420€. It feels reasonable for a project of this caliber that a year’s expenses could buy a capable minicomputer. + +What is a capable minicomputer then, you may ask. Well, my needs are pretty straightforward: + + + + + + + + + + + + + +
Qualitative requirements
UsefulnessCan run a range of small to medium load web services without a hitch
TrustworthinessData is durable, disk failures are not catastrophic, I can sleep at night
ExpandabilityCan add more terabytes in the future
Quantitative requirements
CPULow power, economical, can run FreeBSD.
MemoryWon’t OOM swap in day-to-day operation, ZFS is happy
Disk I/OI don’t really care, no high-throughput required. As long as it’s not especially bad (i.e. HDD-level bad).
Disk spaceSlots for multiple SSDs (RAID). Enough space to hold important files and some media with redundancy. No need for data hoarding.
Network2 interfaces (private and public networks), Gigabit ethernet. No need for wireless.
AnnoyanceLow noise, low temps, sleek form factor
+ +With this list of requirements, I handwaved some models and numbers: + +- CPU: Intel N series ⭐️ +- Memory: 16GB +- Disk space: 2TB (usable) + +> ⭐️ Intel N series processors, formally known as [Alder Lake-N](https://en.wikipedia.org/wiki/Alder_Lake#Alder_Lake-N) and [Twin Lake-N](https://en.wikipedia.org/wiki/Alder_Lake#Twin_lake-N), are affordable processors designed to be primarily used in mobile devices, lower end laptops (think Chromebooks) and small workstations. Some models, such as the N100, N150, N200 and N250, have thermal power ratings as low as 6W, making them popular in the passively-cooled, quasi-embedded design space: routers, NAS's, and the like. + +I got the idea for an Intel N series chip from a friend who has succesfully set up computing cluster from a bunch of small machines with those chips. They seemed to be just what I was looking for. + +With these, I sail off towards the east (AliExpress). + +By the way, I also looked at ARM and RISC-V chips for this. I figured that if Apple Silicon can work as well as it does while being based on ARM, what's stopping me from reaping the same benefits, at least in part? ARM has T1 support from FreeBSD. + +Turns out that there simply aren't any reasonably priced machines based on chips comparable to e.g. the Intel N150. Everything's either designed for mobile devices or just prohibitively expensive. Maybe there is no market yet for small non-x64 computers? + +## Sourcing the silicon + +AliExpress, an online retailer based in China, is nice since they have a huge variety of devices at prices that would never be profitable if sold in a western shop. I would prefer to buy European in this current political atmosphere, but I'm working off a budget, and limiting my options to reputable and ethical vendors would force me to relax my requirements a lot or stop the project in its tracks altogether. + +{{ fig(src="aliexpress_minipc_results.png", alt='Some of the results of a "mini pc" search query') }} + +I'm happy to notice that there is a varied selection of devices at the ~200€ price point. Some are designed to be network devices, with a bunch of physical interfaces and limited storage options, while some are clearly NAS oriented, with SATA 3.5" drive bays capable of housing two or four disks, most commonly. + +I'm looking for something in the middle ground: I need two physical interfaces (a property of router-likes) but I also need storage, preferably of the solid state variety (a property of NAS-likes). + +**Conclusion**: I'm going with this one (possibly stale link warning): [Topton Pocket Mini PC](https://www.aliexpress.com/item/1005007905020917.html). With some mystery promotions applied, I had to only pay 162.27€, with free shipping. I also purchased 16GB of RAM (Crucial DDR5 SODIMM 4800MHz) separately, also from AliExpress. + +> AliExpress tends to have multiple layers of ‼️ LIMITED SALE ‼️ and 🤑 TOP DEAL COUPON 🤑 going on at the same time. In the mobile app they also have a bunch of connect-four-type games that can score you big AliExpress Coins©. Please continue consuming. + +{{ fig(src="aliexpress_minipc.png", alt="A promotional image of the mini PC I picked, hosted here in case the product page goes poof. Please enjoy the broken English.") }} + +Regarding disks, I think I want to buy them locally, just in case there are issues with them. It's way less stressful to handle returns and such with a local vendor than with an overseas megacorp. I feel that stuff like RAM is pretty often OK even if bought from less reputable sources, but with disks I don't want to test my luck. + +**Conclusion**: I'm going to get 4 x 1 TB (double the required usable space because of mirroring) of basic NVMe drives from a computer parts shop (Gigantti) nearby. + +## Setting up hardware + +The box arrives after a bit of a wait (par for the course for the cheapest delivery option, China Airmail). Everything seems to be in order content-wise, but there is no installation manual whatsoever included. I would like one, since I need to figure out how to install the RAM stick. It seems to somehow go below the NVMe sticks, but there is no obvious mechanism for detaching the NVMe bay. + +However, with a bit of trial and error I find the correct screw to remove. The top layer comes off, revealing the RAM stick slot underneath. Very carefully I slot in the memory and push down the clamp that fixes it in place. + +With similar care I put the drive bay back in, and connect the four NVMe drives and stick on their respective thermal material stickers that conduct heat for the drives to the heatsink on top of the machine. I screw everything in place with the tiny screws that came with the machine. A magnetic screwdriver would be a godsend now! + +After a bit, everything's put together. After connecting the power cable, the machine powers on without me even touching the power switch, which is a bit surprising, but ok. I hear the POST beep. + +Success! + +## To be continued + +In the next part(s) we will be looking at installing FreeBSD for the first (and second) time, and networking. I had some – let's say curious – issues with DHCP leases from the ISP. We might also take a look at my jail setup. diff --git a/content/posts/home-server-part-3.md b/content/posts/home-server-part-3.md deleted file mode 100644 index 456ee2a..0000000 --- a/content/posts/home-server-part-3.md +++ /dev/null @@ -1,77 +0,0 @@ ---- -title: "A home server journey, part 3: Installation" -date: 2025-12-23 -description: | - This is the third episode in a series where I set up a FreeBSD home server, explaining all the steps, problems and solutions along the way. This time we're installing the OS for the first time. -extra: - kind: note ---- - -This is the third episode in a series where I set up a FreeBSD home server, explaining all the steps, problems and solutions along the way. This time we're installing the OS for the first time. - -Check out episodes [1](/posts/home-server-part-1) and [2](/posts/home-server-part-2). - -{{ toc() }} - -## Installing FreeBSD - -Installation should be pretty straightforward. I'm going to go with the latest stable release installer (`14.3-RELEASE`) in the `amd64` + `memstick.iso` variety. Let's burn the installer onto a memory stick, plug it in, and was enter the install wizard. - -> Note from the future! `15.0-RELEASE` is already out, and I have recently updated to it. This post is already outdated! - -The initial idea is this: - -- ZFS, for all the automatic durability gains and easy-to-setup RAID1+0 (stripe of mirrors) -- encrypted ZFS datasets (kinda similar to partitions) for **jails**, which would contain all the actual services -- unencrypted root, so that I can reboot the machine remotely, physically unattended - -> [Jails](https://docs.freebsd.org/en/books/handbook/jails/) are a lightweight way to containerize applications. They can get a separate process space, network stack, file system, root user, among others. They use the same kernel as the host, so they are more similar to LXC or OCI containers (think Docker, but more mature and flexible) than to virtual machines, which in FreeBSD are managed with [bhyve](https://docs.freebsd.org/en/books/handbook/virtualization/#virtualization-host-bhyve). -> -> Teaser: we will be installing `bhyve` later too. - -Using the install wizard, I set up the system following my rough plan. And here we go, all up and running: - -{{ fig(src="/files/pursotin_fastfetch.png", alt="Fastfetch output showing the machine specs. Disregard the uptime (I took the screenshot way later)") }} - -### RAIDZ - -You might notice that I bought 4 x 1 TB of NVMe disks, but `fastfetch` is only showing ~2TB. That's because I set up the disks into a "RAID 1+0" configuration: - -{{ fig(src="/files/pursotin_raidz.svg", alt='A "RAID 1+0" configuration') }} - -RAID 1+0 is a combination of two RAID levels. RAID 1, or _mirror_, consumes two disks to produce one virtual disk that has the capacity of only one physical disk but can withstand the loss of either one. ZFS can automatically heal missing data in a member of a mirror by copying it over from the healthy member. RAID 0, or _stripe_, consumes two disks to produce a disk that has the capacity of the sum of its member capacities as well as double I/O speed, but fails if either member disk fails. The combination of these is a useful way to get increased speed and increased durability at the price of half your raw capacity. - -## Threat profile and encryption - -To figure out what level and what kind of at-rest encryption I need, I need to stop and think about the threat model. - -Scenario A, _online intruder_. An attacker that gains shell access can exfiltrate any data that the user has access to, since the decryption key has been activated at boot. At-rest encryption won't help here. - -Scenario B, _burglar_. An intruder grabs the machine and brings it to a place where they can inspect the disks, possibly with sophisticated recovery tools. - -- In a non-encrypted-root install, everything outside the encrypted jails is instantly accessible. Stored API keys and similar secrets leak. The administrator must take great care not to keep _anything_ of value on the unencrypted partition(s), and store everything in encrypted datasets. -- An encrypted root would be safe from this attack, assuming the cryptography used in the encryption is bulletproof. - -Scenario C, _evil maid attack_. A friend, spouse or similar tampers with the unattended physical system. Illegitimate access is blocked by requiring login and keeping the credentials in my personal vault. If the whole disk is encrypted, the worst thing the attacker can do is cause temporary harm and possibly data loss by powering off the system. If not, the attacker can do all kinds of nasty things, like booting a live environment and copying the disk contents, replacing the kernel/bootloader, installing malware etc. - -## Conclusion - -It makes sense to encrypt the whole thing. - -⚠️ But wait a minute! This is against my initial idea: - -> unencrypted root, so that I can reboot the machine remotely, physically unattended - -Encrypting the whole disk loses the ability to do unattended reboots. A bit inconvenient, but it is what it is. I'll just make sure to only reboot when I'm physically near the machine, which should be often, considering I work from home and the server is, uh, right there. - -Because of this architectural change, I ended up installing FreeBSD for a second time, now with full disk encryption. - -## System disk? - -Sometimes a separate small system disk is used in addition to a redundant array of "data disks". I decided to not pursue this setup, since then the system would not benefit from the RAIDZ redundancy and self-healing capabilities. In fact, I'd imagine that the ability to repair the OS and packages is even more important to me than repairing bulk data. - -I decided to keep the system next to the data on the disks (a "traditional" install). - -## To be continued - -In the next part(s) we will be looking at networking. I had some – let's say curious – issues with DHCP leases from the ISP. We'll also take a look at my jail setup. diff --git a/content/posts/home-server-part-3/index.md b/content/posts/home-server-part-3/index.md new file mode 100644 index 0000000..0b553ba --- /dev/null +++ b/content/posts/home-server-part-3/index.md @@ -0,0 +1,77 @@ +--- +title: "A home server journey, part 3: Installation" +date: 2025-12-23 +description: | + This is the third episode in a series where I set up a FreeBSD home server, explaining all the steps, problems and solutions along the way. This time we're installing the OS for the first time. +extra: + kind: note +--- + +This is the third episode in a series where I set up a FreeBSD home server, explaining all the steps, problems and solutions along the way. This time we're installing the OS for the first time. + +Check out episodes [1](/posts/home-server-part-1) and [2](/posts/home-server-part-2). + +{{ toc() }} + +## Installing FreeBSD + +Installation should be pretty straightforward. I'm going to go with the latest stable release installer (`14.3-RELEASE`) in the `amd64` + `memstick.iso` variety. Let's burn the installer onto a memory stick, plug it in, and was enter the install wizard. + +> Note from the future! `15.0-RELEASE` is already out, and I have recently updated to it. This post is already outdated! + +The initial idea is this: + +- ZFS, for all the automatic durability gains and easy-to-setup RAID1+0 (stripe of mirrors) +- encrypted ZFS datasets (kinda similar to partitions) for **jails**, which would contain all the actual services +- unencrypted root, so that I can reboot the machine remotely, physically unattended + +> [Jails](https://docs.freebsd.org/en/books/handbook/jails/) are a lightweight way to containerize applications. They can get a separate process space, network stack, file system, root user, among others. They use the same kernel as the host, so they are more similar to LXC or OCI containers (think Docker, but more mature and flexible) than to virtual machines, which in FreeBSD are managed with [bhyve](https://docs.freebsd.org/en/books/handbook/virtualization/#virtualization-host-bhyve). +> +> Teaser: we will be installing `bhyve` later too. + +Using the install wizard, I set up the system following my rough plan. And here we go, all up and running: + +{{ fig(src="pursotin_fastfetch.png", alt="Fastfetch output showing the machine specs. Disregard the uptime (I took the screenshot way later)") }} + +### RAIDZ + +You might notice that I bought 4 x 1 TB of NVMe disks, but `fastfetch` is only showing ~2TB. That's because I set up the disks into a "RAID 1+0" configuration: + +{{ fig(src="pursotin_raidz.svg", alt='A "RAID 1+0" configuration') }} + +RAID 1+0 is a combination of two RAID levels. RAID 1, or _mirror_, consumes two disks to produce one virtual disk that has the capacity of only one physical disk but can withstand the loss of either one. ZFS can automatically heal missing data in a member of a mirror by copying it over from the healthy member. RAID 0, or _stripe_, consumes two disks to produce a disk that has the capacity of the sum of its member capacities as well as double I/O speed, but fails if either member disk fails. The combination of these is a useful way to get increased speed and increased durability at the price of half your raw capacity. + +## Threat profile and encryption + +To figure out what level and what kind of at-rest encryption I need, I need to stop and think about the threat model. + +Scenario A, _online intruder_. An attacker that gains shell access can exfiltrate any data that the user has access to, since the decryption key has been activated at boot. At-rest encryption won't help here. + +Scenario B, _burglar_. An intruder grabs the machine and brings it to a place where they can inspect the disks, possibly with sophisticated recovery tools. + +- In a non-encrypted-root install, everything outside the encrypted jails is instantly accessible. Stored API keys and similar secrets leak. The administrator must take great care not to keep _anything_ of value on the unencrypted partition(s), and store everything in encrypted datasets. +- An encrypted root would be safe from this attack, assuming the cryptography used in the encryption is bulletproof. + +Scenario C, _evil maid attack_. A friend, spouse or similar tampers with the unattended physical system. Illegitimate access is blocked by requiring login and keeping the credentials in my personal vault. If the whole disk is encrypted, the worst thing the attacker can do is cause temporary harm and possibly data loss by powering off the system. If not, the attacker can do all kinds of nasty things, like booting a live environment and copying the disk contents, replacing the kernel/bootloader, installing malware etc. + +## Conclusion + +It makes sense to encrypt the whole thing. + +⚠️ But wait a minute! This is against my initial idea: + +> unencrypted root, so that I can reboot the machine remotely, physically unattended + +Encrypting the whole disk loses the ability to do unattended reboots. A bit inconvenient, but it is what it is. I'll just make sure to only reboot when I'm physically near the machine, which should be often, considering I work from home and the server is, uh, right there. + +Because of this architectural change, I ended up installing FreeBSD for a second time, now with full disk encryption. + +## System disk? + +Sometimes a separate small system disk is used in addition to a redundant array of "data disks". I decided to not pursue this setup, since then the system would not benefit from the RAIDZ redundancy and self-healing capabilities. In fact, I'd imagine that the ability to repair the OS and packages is even more important to me than repairing bulk data. + +I decided to keep the system next to the data on the disks (a "traditional" install). + +## To be continued + +In the next part(s) we will be looking at networking. I had some – let's say curious – issues with DHCP leases from the ISP. We'll also take a look at my jail setup. diff --git a/content/posts/home-server-part-3/pursotin_fastfetch.png b/content/posts/home-server-part-3/pursotin_fastfetch.png new file mode 100644 index 0000000..241f5e9 Binary files /dev/null and b/content/posts/home-server-part-3/pursotin_fastfetch.png differ diff --git a/content/posts/home-server-part-3/pursotin_raidz.svg b/content/posts/home-server-part-3/pursotin_raidz.svg new file mode 100644 index 0000000..d2942ac --- /dev/null +++ b/content/posts/home-server-part-3/pursotin_raidz.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/content/posts/home-server-part-4.md b/content/posts/home-server-part-4.md deleted file mode 100644 index 1eff36f..0000000 --- a/content/posts/home-server-part-4.md +++ /dev/null @@ -1,124 +0,0 @@ ---- -title: "A home server journey, part 4: Network" -date: 2025-12-25 -description: | - This is the fourth episode in a series where I set up a FreeBSD home server, explaining all the steps, problems and solutions along the way. This time we're setting up the networking. -extra: - kind: note ---- - -This is the fourth episode in a series where I set up a FreeBSD home server, explaining all the steps, problems and solutions along the way. This time we're setting up the networking. - -Check out episodes [1](/posts/home-server-part-1), [2](/posts/home-server-part-2), [3](/posts/home-server-part-3). - -{{ toc() }} - -## Dual interfaces - -I already have a private network (`192.168.0.0/16`) that uses my pfSense router as its gateway. The FreeBSD server will connect to this network through ethernet interface number one (`lan0`). Packets to and from local devices go through this interface. - -The host will also be connected directly to the ISP's network using its second interface (`wan0`). This interface will get its IP using DHCP. Packets to the internet, as well as packets to public services running on the FreeBSD host go through this interface. - -{{ fig(src="/files/pursotin_network.svg", alt="A simplified diagram showing the two interfaces") }} - -A dual interface set up like this is simple in theory, but there are important nuances, e.g. regarding return traffic routing and DNS. - -## Routing - -If we send a packet out through `eth0`, we should expect return traffic to arrive on `eth0` (and vice versa). However, it is rather easy to misconfigure the system. - -Assume we have a public service, such as an `nginx` web server, which listens on the public interface. If we set up `rc.conf` naively like this: - -```sh -defaultrouter=192.168.0.1 -``` - -...the request might arrive through the public interface, but the response from the web server leaves through the private interface since we have configured the private gateway as the default. This is called _asymmetric routing_. - -This might work! Both routes lead to the internet. From the perspective of the ISP, it doesn't really matter whether the packet went through the home router or not, in this case. - -Sometimes however, this setup leads to the most vexing of networking issues. Figuring out what's wrong taught me a bunch about DHCP and layer 3 routing. - -### Routing issue \#1: ISP DHCP server and MAC addresses - -A DHCP server can keep track of clients using their MAC address. Separate physical interfaces have separate MAC addresses, so there is an obvious problem here. If an initial DHCP request broadcast leaves through one interface, and following DHCP requests (non-broadcast) leave through another interface, the DHCP server might see this as a forged packet. - -This happened to me because of the `defaultrouter` setting: - -1. The initial broadcast leaves through the `wan0` interface correctly. An IP is received. -2. When the lease is about to expire, a targeted request is sent to the now known DHCP server address. This is routed through `lan0` and `192.168.0.1`, since it's the `defaultrouter`. -3. The DHCP server sees the FreeBSD WAN MAC on first contact, and the home router MAC address on second contact. The lease is not renewed. -4. The lease expires, repeat from step 1. - -My solution? Just don't use `defaultrouter`. As part of the routine DHCP song and dance, `dhclient` adds a valid default route to the system routing table. Just use that for all internet-bound traffic. You can see the routes with `netstat -rn`: - -```sh -# netstat -rn -Routing tables - -Internet: -Destination Gateway Flags Netif Expire -default 87.92.64.1 UGS wan0 -87.92.64.0/18 link#36 U wan0 -192.168.0.0/16 link#25 U lan0 -127.0.0.1 link#57 UH lo0 -... -``` - -The `default` route was added by DHCP. - -### Routing issue \#2: DNS-level ad blocker in my router - -I run [pfBlockerNG](https://docs.netgate.com/pfsense/en/latest/packages/pfblocker.html) on my router to block ads on a DNS level (think Pi-hole). If I route traffic through the router, the return traffic is processed by this extra firewall. Some packets got dropped by pfBlockerNG and never arrived at the destination. - -This was very annoying to debug! At least I had logging on, so I could confirm what's happening pretty quickly. - -## DNS - -Nameservers are defined on a whole system basis in `/etc/resolv.conf`, and not per interface or per IP. I have a basic DNS resolver running on my home server, so I simply configured my `resolv.conf` as: - -```sh -nameserver 192.168.0.1 -``` - -### Split horizon - -Which IP should be returned from the DNS server if I query `some-public-service.jan.systems`? The private address from `192.168.0.0/16`, or a public IP reachable from the internet? - -This is called **split horizon** DNS. One approach is to configure the DNS server to respond differently based on the address of the querying host. A private network host gets the private IP, etc. - -I decided to not worry about this, and just use a separate domain, `local.jan.systems` for local addresses. This is a bit inconvenient, but I don't mind. The fewer DNS problems the better. - -### DHCP and `resolv.conf` - -By default, `dhclient` has a hook that overwrites `resolv.conf` with the DHCP-provided nameserver information. I want to always use my router as the DNS server, so I had to disable this functionality. - -On FreeBSD, there are two simple solutions: - -1. `resolvconf.conf`: a meta-config that configures the `resolvconf` tool, which is used internally by `dhclient` - -To disable `resolvconf` altogether, add this to `/etc/resolvconf.conf`: - -```sh -resolvconf=NO -``` - -Now, `resolvconf`, and transitively `dhclient`, won't overwrite your changes anymore. - -2. making `/etc/resolv.conf` immutable with a flag - -As root, you can set the file as immutable with `chflags`. Then, nothing can edit the file before the flag is removed. - -```sh -chflags schg /etc/resolv.conf -``` - -Note that if you have a non-default [`securelevel`](https://man.freebsd.org/cgi/man.cgi?securelevel), you might not be able to remove this flag. - -## Dual stack? - -For now, I'm building everything on IPv4. My ISP has no support for SLAAC which makes IPv6 somewhat non-trivial. I will most likely add IPv6 at some point, but it's not a priority. - -## To be continued - -In the next part(s) we will be looking at provisioning with Ansible. diff --git a/content/posts/home-server-part-4/index.md b/content/posts/home-server-part-4/index.md new file mode 100644 index 0000000..bb44702 --- /dev/null +++ b/content/posts/home-server-part-4/index.md @@ -0,0 +1,124 @@ +--- +title: "A home server journey, part 4: Networking" +date: 2025-12-24 +description: | + This is the fourth episode in a series where I set up a FreeBSD home server, explaining all the steps, problems and solutions along the way. This time we're looking at networking. +extra: + kind: note +--- + +This is the fourth episode in a series where I set up a FreeBSD home server, explaining all the steps, problems and solutions along the way. This time we're looking at networking. + +Check out episodes [1](/posts/home-server-part-1), [2](/posts/home-server-part-2), [3](/posts/home-server-part-3). + +{{ toc() }} + +## Dual interfaces + +I already have a private network (`192.168.0.0/16`) that uses my pfSense router as its gateway. The FreeBSD server will connect to this network through ethernet interface number one (`lan0`). Packets to and from local devices go through this interface. + +The host will also be connected directly to the ISP's network using its second interface (`wan0`). This interface will get its IP using DHCP. Packets to the internet, as well as packets to public services running on the FreeBSD host go through this interface. + +{{ fig(src="pursotin_network.svg", alt="A simplified diagram showing the two interfaces") }} + +A dual interface set up like this is simple in theory, but there are important nuances, e.g. regarding return traffic routing and DNS. + +## Routing + +If we send a packet out through `eth0`, we should expect return traffic to arrive on `eth0` (and vice versa). However, it is rather easy to misconfigure the system. + +Assume we have a public service, such as an `nginx` web server, which listens on the public interface. If we set up `rc.conf` naively like this: + +```sh +defaultrouter=192.168.0.1 +``` + +...the request might arrive through the public interface, but the response from the web server leaves through the private interface since we have configured the private gateway as the default. This is called _asymmetric routing_. + +This might work! Both routes lead to the internet. From the perspective of the ISP, it doesn't really matter whether the packet went through the home router or not, in this case. + +Sometimes however, this setup leads to the most vexing of networking issues. Figuring out what's wrong taught me a bunch about DHCP and layer 3 routing. + +### Routing issue \#1: ISP DHCP server and MAC addresses + +A DHCP server can keep track of clients using their MAC address. Separate physical interfaces have separate MAC addresses, so there is an obvious problem here. If an initial DHCP request broadcast leaves through one interface, and following DHCP requests (non-broadcast) leave through another interface, the DHCP server might see this as a forged packet. + +This happened to me because of the `defaultrouter` setting: + +1. The initial broadcast leaves through the `wan0` interface correctly. An IP is received. +2. When the lease is about to expire, a targeted request is sent to the now known DHCP server address. This is routed through `lan0` and `192.168.0.1`, since it's the `defaultrouter`. +3. The DHCP server sees the FreeBSD WAN MAC on first contact, and the home router MAC address on second contact. The lease is not renewed. +4. The lease expires, repeat from step 1. + +My solution? Just don't use `defaultrouter`. As part of the routine DHCP song and dance, `dhclient` adds a valid default route to the system routing table. Just use that for all internet-bound traffic. You can see the routes with `netstat -rn`: + +```sh +# netstat -rn +Routing tables + +Internet: +Destination Gateway Flags Netif Expire +default 87.92.64.1 UGS wan0 +87.92.64.0/18 link#36 U wan0 +192.168.0.0/16 link#25 U lan0 +127.0.0.1 link#57 UH lo0 +... +``` + +The `default` route was added by DHCP. + +### Routing issue \#2: DNS-level ad blocker in my router + +I run [pfBlockerNG](https://docs.netgate.com/pfsense/en/latest/packages/pfblocker.html) on my router to block ads on a DNS level (think Pi-hole). If I route traffic through the router, the return traffic is processed by this extra firewall. Some packets got dropped by pfBlockerNG and never arrived at the destination. + +This was very annoying to debug! At least I had logging on, so I could confirm what's happening pretty quickly. + +## DNS + +Nameservers are defined on a whole system basis in `/etc/resolv.conf`, and not per interface or per IP. I have a basic DNS resolver running on my home server, so I simply configured my `resolv.conf` as: + +```sh +nameserver 192.168.0.1 +``` + +### Split horizon + +Which IP should be returned from the DNS server if I query `some-public-service.jan.systems`? The private address from `192.168.0.0/16`, or a public IP reachable from the internet? + +This is called **split horizon** DNS. One approach is to configure the DNS server to respond differently based on the address of the querying host. A private network host gets the private IP, etc. + +I decided to not worry about this, and just use a separate domain, `local.jan.systems` for local addresses. This is a bit inconvenient, but I don't mind. The fewer DNS problems the better. + +### DHCP and `resolv.conf` + +By default, `dhclient` has a hook that overwrites `resolv.conf` with the DHCP-provided nameserver information. I want to always use my router as the DNS server, so I had to disable this functionality. + +On FreeBSD, there are two simple solutions: + +1. `resolvconf.conf`: a meta-config that configures the `resolvconf` tool, which is used internally by `dhclient` + +To disable `resolvconf` altogether, add this to `/etc/resolvconf.conf`: + +```sh +resolvconf=NO +``` + +Now, `resolvconf`, and transitively `dhclient`, won't overwrite your changes anymore. + +2. making `/etc/resolv.conf` immutable with a flag + +As root, you can set the file as immutable with `chflags`. Then, nothing can edit the file before the flag is removed. + +```sh +chflags schg /etc/resolv.conf +``` + +Note that if you have a non-default [`securelevel`](https://man.freebsd.org/cgi/man.cgi?securelevel), you might not be able to remove this flag. + +## Dual stack? + +For now, I'm building everything on IPv4. My ISP has no support for SLAAC which makes IPv6 somewhat non-trivial. I will most likely add IPv6 at some point, but it's not a priority. + +## To be continued + +In the next part(s) we will be looking at provisioning with Ansible. diff --git a/content/posts/home-server-part-4/pursotin_network.svg b/content/posts/home-server-part-4/pursotin_network.svg new file mode 100644 index 0000000..79d1dea --- /dev/null +++ b/content/posts/home-server-part-4/pursotin_network.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/content/posts/it-doesnt-mean-that.md b/content/posts/it-doesnt-mean-that.md deleted file mode 100644 index adf171f..0000000 --- a/content/posts/it-doesnt-mean-that.md +++ /dev/null @@ -1,42 +0,0 @@ ---- -title: It doesn't mean that -date: 2024-03-31 -extra: - kind: note ---- - -A plain circle with a small dot in the center - -## When I learned my first programming language - -I learned about functions. - -A _function_ is a thing you can call. The function might then _return_ a value. - -## Then I learned about functional programming - -And learned that those words did not mean that at all. Or at least that was not their full, universal meaning. - -Those _functions_ were actually _procedures_. Or _subroutines_. A function should be mathematical and pure. A mapping from a type to another. - -_Returning_ was not optional. All functions return, explicitly or implicitly, since function application must be assigned a value. - -I learned new words too. - -_Functors_ are containers. _Monads_ are powered-up functors with useful computational context. - -## Now that I’m learning about category theory… - -…I’m learning that those words do not mean what I thought they meant, either. - -_Functions_ are apparently morphisms, or arrows, in the category of types. _Functors_ are not containers, they are morphisms between categories. They are also their own category. - -_Monads_ are — I haven’t gotten to monads yet. - -## I read that they have a Monad in philosophy - -And it means _god_. [¹](it-doesnt-mean-that#fn1) - ---- - -1. More or less. diff --git a/content/posts/it-doesnt-mean-that/index.md b/content/posts/it-doesnt-mean-that/index.md new file mode 100644 index 0000000..0fb2988 --- /dev/null +++ b/content/posts/it-doesnt-mean-that/index.md @@ -0,0 +1,40 @@ +--- +title: It doesn't mean that +date: 2024-05-16 +--- + +A plain circle with a small dot in the center + +## When I learned my first programming language + +I learned about functions. + +A _function_ is a thing you can call. The function might then _return_ a value. + +## Then I learned about functional programming + +And learned that those words did not mean that at all. Or at least that was not their full, universal meaning. + +Those _functions_ were actually _procedures_. Or _subroutines_. A function should be mathematical and pure. A mapping from a type to another. + +_Returning_ was not optional. All functions return, explicitly or implicitly, since function application must be assigned a value. + +I learned new words too. + +_Functors_ are containers. _Monads_ are powered-up functors with useful computational context. + +## Now that I’m learning about category theory… + +…I’m learning that those words do not mean what I thought they meant, either. + +_Functions_ are apparently morphisms, or arrows, in the category of types. _Functors_ are not containers, they are morphisms between categories. They are also their own category. + +_Monads_ are — I haven’t gotten to monads yet. + +## I read that they have a Monad in philosophy + +And it means _god_. [¹](it-doesnt-mean-that#fn1) + +--- + +1. More or less. diff --git a/content/posts/it-doesnt-mean-that/wikimedia_monad.svg b/content/posts/it-doesnt-mean-that/wikimedia_monad.svg new file mode 100644 index 0000000..887c3c6 --- /dev/null +++ b/content/posts/it-doesnt-mean-that/wikimedia_monad.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/content/posts/now-2024-fall.md b/content/posts/now-2024-fall.md deleted file mode 100644 index b71de95..0000000 --- a/content/posts/now-2024-fall.md +++ /dev/null @@ -1,88 +0,0 @@ ---- -title: 🍁 Now, fall 2024 -date: 2024-12-10 -description: Dog dug up the yard and the new grass. Painted some rooms. Built a little bathroom shelf, a little nightstand and a sound diffuser. -extra: - kind: now ---- - -{{ toc() }} - -## Home - -- Dog dug up the yard and the new grass. Multiple times even. Planted some clovers to hide the bald spot. Fixed up a vine that fell down from the wall. -- Painted some rooms. The previous owner left them in a sorry state. -- Built a little bathroom shelf, a little nightstand and a sound diffuser. Getting the hang of woodworking. - - - - -## Relationship - -- Got engaged! - -## Quests - -I remember seeing some blog post somewhere that advocated, possibly a bit ironically, the concept of using **main quests**, **side quests** and **daily quests** in tracking your to-dos. I decided to give them a try. I now use main and side quests to keep track of my higher level objectives. These objectives function at the week or month level. I do not use daily quests. - -I’ve always found it hard to stick to to-do tracking schemes. They are often too complex and unable to track priorities or time spans in a way that would feel natural. Maybe this approach is simple and video-gamey enough to stick. - -## Live music 🤘🏻 - -- Valkeat, Ethereal Sin @ Lepakkomies -- Queendyster, Liturgy @ Kuudes Linja -- Havukruunu, Satanic North @ Tavastia - -## Video games - -Played: - -- Outer Wilds (this was a good game) and the DLC (not entirely my vibe) -- Plucky Squire -- Neon White - -Enjoyed watching: - -- Metal Gear Solids 1–4 in [Giant Bomb longplay format](https://www.youtube.com/playlist?list=PLXlhzeWIuTHLfGvMnnWLJK3DRLQwcTpwd) -- Ultrakill - -## Career - -- Finished some certifications: _GCP-ACE_, _AZ-900_, _AI-900_. - -## Reading - -- Still reading _Designing Data-intensive Applications_. Got sidetracked by a database project that was inspired by the book. - -## Top tunes - -- Djennaration – Liturgy -- The Ultrakill OST - -## Previous update - -- [🌞 Now, summer 2024](/posts/now-2024-summer) diff --git a/content/posts/now-2024-fall/diffuser_1.jpg b/content/posts/now-2024-fall/diffuser_1.jpg new file mode 100644 index 0000000..d5e6693 Binary files /dev/null and b/content/posts/now-2024-fall/diffuser_1.jpg differ diff --git a/content/posts/now-2024-fall/diffuser_2.jpg b/content/posts/now-2024-fall/diffuser_2.jpg new file mode 100644 index 0000000..f8c8b84 Binary files /dev/null and b/content/posts/now-2024-fall/diffuser_2.jpg differ diff --git a/content/posts/now-2024-fall/diffuser_3.jpg b/content/posts/now-2024-fall/diffuser_3.jpg new file mode 100644 index 0000000..5efa8ab Binary files /dev/null and b/content/posts/now-2024-fall/diffuser_3.jpg differ diff --git a/content/posts/now-2024-fall/index.md b/content/posts/now-2024-fall/index.md new file mode 100644 index 0000000..1946eda --- /dev/null +++ b/content/posts/now-2024-fall/index.md @@ -0,0 +1,88 @@ +--- +title: 🍁 Now, fall 2024 +date: 2024-12-10 +description: Dog dug up the yard and the new grass. Painted some rooms. Built a little bathroom shelf, a little nightstand and a sound diffuser. +extra: + kind: now +--- + +{{ toc() }} + +## Home + +- Dog dug up the yard and the new grass. Multiple times even. Planted some clovers to hide the bald spot. Fixed up a vine that fell down from the wall. +- Painted some rooms. The previous owner left them in a sorry state. +- Built a little bathroom shelf, a little nightstand and a sound diffuser. Getting the hang of woodworking. + + + + +## Relationship + +- Got engaged! + +## Quests + +I remember seeing some blog post somewhere that advocated, possibly a bit ironically, the concept of using **main quests**, **side quests** and **daily quests** in tracking your to-dos. I decided to give them a try. I now use main and side quests to keep track of my higher level objectives. These objectives function at the week or month level. I do not use daily quests. + +I’ve always found it hard to stick to to-do tracking schemes. They are often too complex and unable to track priorities or time spans in a way that would feel natural. Maybe this approach is simple and video-gamey enough to stick. + +## Live music 🤘🏻 + +- Valkeat, Ethereal Sin @ Lepakkomies +- Queendyster, Liturgy @ Kuudes Linja +- Havukruunu, Satanic North @ Tavastia + +## Video games + +Played: + +- Outer Wilds (this was a good game) and the DLC (not entirely my vibe) +- Plucky Squire +- Neon White + +Enjoyed watching: + +- Metal Gear Solids 1–4 in [Giant Bomb longplay format](https://www.youtube.com/playlist?list=PLXlhzeWIuTHLfGvMnnWLJK3DRLQwcTpwd) +- Ultrakill + +## Career + +- Finished some certifications: _GCP-ACE_, _AZ-900_, _AI-900_. + +## Reading + +- Still reading _Designing Data-intensive Applications_. Got sidetracked by a database project that was inspired by the book. + +## Top tunes + +- Djennaration – Liturgy +- The Ultrakill OST + +## Previous update + +- [🌞 Now, summer 2024](/posts/now-2024-summer) diff --git a/content/posts/now-2024-spring.md b/content/posts/now-2024-spring.md deleted file mode 100644 index aa1fe1f..0000000 --- a/content/posts/now-2024-spring.md +++ /dev/null @@ -1,87 +0,0 @@ ---- -title: 🌱 Now, spring 2024 -date: 2024-04-22 -description: Inches of snow are falling on the yard, covering any new green growths. The dog likes it, I don't. -extra: - kind: now ---- - -Inches of snow are falling on the yard, covering any new green growths. The dog likes it, I don't. - - - -{{ toc() }} - -## Travel - -- Attended the [QCon London 2024](/posts/qcon-london-2024-takeaways) in London -- Went to [Tahko](https://www.tahko.com/en/) for a company leisure trip to downhill ski and snowmobile - -## Music - -[BLEK](/projects#blek) was live for 3 hours on student radio Radiodiodi with a dungeon synth genre deepdive program. According to metrics, 20 people (!) were listening to our show, which was also our grand EP premiere. Big leagues. - -I bought a [cassette player](/posts/cassettes) and a bunch of metal and dungeon synth tapes, as well some empty ones from the Bezos shop. I'm planning to record BLEK's music on some of them. - -## Games - -We've been on a proper Ratchet and Clank binge, together with Marika. - -- Ratchet and Clank: Rift Apart -- Ratchet and Clank: Tools of Destruction -- Ratchet and Clank: Gladiator (Deadlocked) -- Also we played the original trilogy in 2023 - -Non-Ratchet: - -- Rollerdrome - -Tabletop games: - -- Started playing [Miru](https://hinokodo.itch.io/miru-an-analog-adventure-game) - -## Reading - -- _Category Theory for Programmers_ by Bartosz Milewski - - Turned out to be quite a bit more difficult than I expected. The first half was completely doable with my functional programming and limited math background, but the second half is a piece of work. I'll push through though. - -## Movies - -I went to see Dune 2. It was as ok as the first part. - -## Projects - -### ATK16 - -Picked up [ATK16](/projects/atk16) again, adding some major stuff: - -- A calling convention for functions: arguments in registers `RA`–`RG`, return value in register `RG` -- An emulator & step debugger in Python + Pygame for easier debugging -- A bump allocator for using the heap -- Ergonomics features in the assembler: - - A `@data` directive for storing data in a data segment - - A string datatype that is encoded as a length and data bytes in memory -- Some "standard library" stuff, like `memset` and `memcopy` -- A hardware monitor / shell-like program for interacting with the system in text mode -- An MMIO register for setting the interrupt flag in order to define critical sections for primitive interrupt concurrency - -I feel like a proper introduction post / documentation is long overdue 😅 I should fix that. - -### Garden (jan.systems) - -Rewrote the site generator flow. Previously, each file in the content directory was accessed and processed from start to finish separately. This meant that there was no knowledge of other notes available when rendering a note. This made stuff like a [_dynamic now page_](https://derekkedziora.com/blog/dynamic-now-page) impossible. - -Now, the flow starts by traversing the entire content directory and indexing it into an in-memory database. The database contains all the metadata and filesystem info of all the notes, as well as static files. This makes dynamic pages possible since the database can be queried at render time. This approach also improved performance, since I could reduce the number of filesystem reads and writes. - -Things I still want to add: - -- A bookmarking system, maybe somehow connected to the ~[liked](/liked) page~? - - _Update from the future_: the liked page was migrated to the [linklog](/linklog). -- A project page structure (just a content thing, no code required) - -## Previous update - -[❄️ Now, winter 2023](/posts/now-2023-winter) diff --git a/content/posts/now-2024-spring/index.md b/content/posts/now-2024-spring/index.md new file mode 100644 index 0000000..ad10fe5 --- /dev/null +++ b/content/posts/now-2024-spring/index.md @@ -0,0 +1,87 @@ +--- +title: 🌱 Now, spring 2024 +date: 2024-04-22 +description: Inches of snow are falling on the yard, covering any new green growths. The dog likes it, I don't. +extra: + kind: now +--- + +Inches of snow are falling on the yard, covering any new green growths. The dog likes it, I don't. + + + +{{ toc() }} + +## Travel + +- Attended the [QCon London 2024](/posts/qcon-london-2024-takeaways) in London +- Went to [Tahko](https://www.tahko.com/en/) for a company leisure trip to downhill ski and snowmobile + +## Music + +[BLEK](/projects#blek) was live for 3 hours on student radio Radiodiodi with a dungeon synth genre deepdive program. According to metrics, 20 people (!) were listening to our show, which was also our grand EP premiere. Big leagues. + +I bought a [cassette player](/posts/cassettes) and a bunch of metal and dungeon synth tapes, as well some empty ones from the Bezos shop. I'm planning to record BLEK's music on some of them. + +## Games + +We've been on a proper Ratchet and Clank binge, together with Marika. + +- Ratchet and Clank: Rift Apart +- Ratchet and Clank: Tools of Destruction +- Ratchet and Clank: Gladiator (Deadlocked) +- Also we played the original trilogy in 2023 + +Non-Ratchet: + +- Rollerdrome + +Tabletop games: + +- Started playing [Miru](https://hinokodo.itch.io/miru-an-analog-adventure-game) + +## Reading + +- _Category Theory for Programmers_ by Bartosz Milewski + - Turned out to be quite a bit more difficult than I expected. The first half was completely doable with my functional programming and limited math background, but the second half is a piece of work. I'll push through though. + +## Movies + +I went to see Dune 2. It was as ok as the first part. + +## Projects + +### ATK16 + +Picked up [ATK16](/projects/atk16) again, adding some major stuff: + +- A calling convention for functions: arguments in registers `RA`–`RG`, return value in register `RG` +- An emulator & step debugger in Python + Pygame for easier debugging +- A bump allocator for using the heap +- Ergonomics features in the assembler: + - A `@data` directive for storing data in a data segment + - A string datatype that is encoded as a length and data bytes in memory +- Some "standard library" stuff, like `memset` and `memcopy` +- A hardware monitor / shell-like program for interacting with the system in text mode +- An MMIO register for setting the interrupt flag in order to define critical sections for primitive interrupt concurrency + +I feel like a proper introduction post / documentation is long overdue 😅 I should fix that. + +### Garden (jan.systems) + +Rewrote the site generator flow. Previously, each file in the content directory was accessed and processed from start to finish separately. This meant that there was no knowledge of other notes available when rendering a note. This made stuff like a [_dynamic now page_](https://derekkedziora.com/blog/dynamic-now-page) impossible. + +Now, the flow starts by traversing the entire content directory and indexing it into an in-memory database. The database contains all the metadata and filesystem info of all the notes, as well as static files. This makes dynamic pages possible since the database can be queried at render time. This approach also improved performance, since I could reduce the number of filesystem reads and writes. + +Things I still want to add: + +- A bookmarking system, maybe somehow connected to the ~[liked](/liked) page~? + - _Update from the future_: the liked page was migrated to the [linklog](/linklog). +- A project page structure (just a content thing, no code required) + +## Previous update + +[❄️ Now, winter 2023](/posts/now-2023-winter) diff --git a/content/posts/now-2024-spring/jojo-in-snow.webm b/content/posts/now-2024-spring/jojo-in-snow.webm new file mode 100644 index 0000000..19047e5 Binary files /dev/null and b/content/posts/now-2024-spring/jojo-in-snow.webm differ diff --git a/content/posts/now-2024-summer.md b/content/posts/now-2024-summer.md deleted file mode 100644 index cc7bb74..0000000 --- a/content/posts/now-2024-summer.md +++ /dev/null @@ -1,56 +0,0 @@ ---- -title: 🌞 Now, summer 2024 -date: 2024-07-18 -extra: - kind: now ---- - -{{ toc() }} - -## Home - -- Yard work: some small landscaping, changed the topsoil to something less dry, and planted grass and flowers. -- Purchased a **wine fridge** for some reason. This led directly to... - -## A new hobby - -- Started learning about wines. Organized a wine tasting. My top pick: Domaine Laroche Chablis 1er Cru Les Vaudevey 2022. - -{{ fig(src="/files/2024-wines.jpg", alt="Wines from the tasting") }} - -### Career - -- I passed the **AWS Certified Developer Associate** certificate exam! A celebratory espresso martini was in order. Planning to do **Solutions Architect Associate** next. - -{{ fig(src="/files/aws-exam-drink.jpeg", alt="My hand holding an espresso martini outside in sunny weather") }} - -## Played games - -- Animal well -- Ratchet & Clank: A Crack in Time -- Snufkin: Melody of Moominvalley -- Cult of the Lamb -- [Miru](https://hinokodo.itch.io/miru-an-analog-adventure-game) -- Celeste - -## Finished reading - -- Finished _Category Theory for Programmers_ by Bartosz Milewski. Had to work through it twice, taking notes on the second pass. I wish I started taking notes from the get go. -- _Wine Simple_ by Aldo Sohm, wine fundamentals for beginners. -- Some CS papers: - - _Do Be Do Be Do_ by Sam Lindley et al. The paper defines a language called Frank that uses a novel algebraic effects based typed effect system instead of monads. Also has types for suspended calculations (think Haskell thunks). Similar to Koka I believe. - - _Cons should not eval its arguments_, a classic that paves the way for lazily evaluated languages. -- Japanese short stories (Yomu Yomu app) such as _人間椅子 (Human chair)_, a story about a carpenter that decides to live inside a chair that he’s made. Adapted to a vocabulary of 1500 words. - -## Started reading - -- _Designing Data-intensive Applications_ by Martin Kleppmann. - -## Top tunes - -- [maudlin of the Well - Gleam in Ranks](https://open.spotify.com/track/5cyKheZd5E3jBtjcflE7Gz?si=y-mOWdUVT36XOo0zl1kK_w&context=spotify%3Aalbum%3A2BwbUYJeuUsv6LUA6GZHB4) -- [predawka - erynias](https://youtu.be/wyOG7ZiVLLo) - -## Previous update - -- [🌱 Now, spring 2024](/posts/now-2024-spring) diff --git a/content/posts/now-2024-summer/2024-wines.jpg b/content/posts/now-2024-summer/2024-wines.jpg new file mode 100644 index 0000000..81cddef Binary files /dev/null and b/content/posts/now-2024-summer/2024-wines.jpg differ diff --git a/content/posts/now-2024-summer/aws-exam-drink.jpeg b/content/posts/now-2024-summer/aws-exam-drink.jpeg new file mode 100644 index 0000000..16f8933 Binary files /dev/null and b/content/posts/now-2024-summer/aws-exam-drink.jpeg differ diff --git a/content/posts/now-2024-summer/index.md b/content/posts/now-2024-summer/index.md new file mode 100644 index 0000000..d716e4a --- /dev/null +++ b/content/posts/now-2024-summer/index.md @@ -0,0 +1,56 @@ +--- +title: 🌞 Now, summer 2024 +date: 2024-07-18 +extra: + kind: now +--- + +{{ toc() }} + +## Home + +- Yard work: some small landscaping, changed the topsoil to something less dry, and planted grass and flowers. +- Purchased a **wine fridge** for some reason. This led directly to... + +## A new hobby + +- Started learning about wines. Organized a wine tasting. My top pick: Domaine Laroche Chablis 1er Cru Les Vaudevey 2022. + +{{ fig(src="2024-wines.jpg", alt="Wines from the tasting") }} + +### Career + +- I passed the **AWS Certified Developer Associate** certificate exam! A celebratory espresso martini was in order. Planning to do **Solutions Architect Associate** next. + +{{ fig(src="aws-exam-drink.jpeg", alt="My hand holding an espresso martini outside in sunny weather") }} + +## Played games + +- Animal well +- Ratchet & Clank: A Crack in Time +- Snufkin: Melody of Moominvalley +- Cult of the Lamb +- [Miru](https://hinokodo.itch.io/miru-an-analog-adventure-game) +- Celeste + +## Finished reading + +- Finished _Category Theory for Programmers_ by Bartosz Milewski. Had to work through it twice, taking notes on the second pass. I wish I started taking notes from the get go. +- _Wine Simple_ by Aldo Sohm, wine fundamentals for beginners. +- Some CS papers: + - _Do Be Do Be Do_ by Sam Lindley et al. The paper defines a language called Frank that uses a novel algebraic effects based typed effect system instead of monads. Also has types for suspended calculations (think Haskell thunks). Similar to Koka I believe. + - _Cons should not eval its arguments_, a classic that paves the way for lazily evaluated languages. +- Japanese short stories (Yomu Yomu app) such as _人間椅子 (Human chair)_, a story about a carpenter that decides to live inside a chair that he’s made. Adapted to a vocabulary of 1500 words. + +## Started reading + +- _Designing Data-intensive Applications_ by Martin Kleppmann. + +## Top tunes + +- [maudlin of the Well - Gleam in Ranks](https://open.spotify.com/track/5cyKheZd5E3jBtjcflE7Gz?si=y-mOWdUVT36XOo0zl1kK_w&context=spotify%3Aalbum%3A2BwbUYJeuUsv6LUA6GZHB4) +- [predawka - erynias](https://youtu.be/wyOG7ZiVLLo) + +## Previous update + +- [🌱 Now, spring 2024](/posts/now-2024-spring) diff --git a/content/posts/now-2025-fall.md b/content/posts/now-2025-fall.md deleted file mode 100644 index fe0d1f2..0000000 --- a/content/posts/now-2025-fall.md +++ /dev/null @@ -1,90 +0,0 @@ ---- -title: 🍁 Now, fall 2025 -date: 2025-10-05 -description: Late summer and early autumn have been a time of ups and downs. -extra: - kind: now ---- - -Late summer and early autumn have been a time of ups and downs. - -I lost my father to illness. The passing was expected, in a way, so I have had time to process things in advance, but when it actually happened it turned everything on its head for a while. - -Sorting out things after the death has taken a lot of time and effort, but things are now more or less in order. I have got a lot of support and help from my peers, and I feel I have let myself process the grief well. I try to focus on positive things. - -We've also been planning the wedding that's taking place in 2026. In many ways I feel out of my element, but things are moving forward. - -{{ toc() }} - -## Home - -A lot of renovations this summer. One of the biggest ones was the translucent roofing installation: - -{{ fig(src="/files/valokate_1.jpg", alt="Installing the sunroof") }} - -After around six hours (including breaks), it's all done! Just in time for rain. - -{{ fig(src="/files/valokate_2.jpg", alt="Complete sunroof") }} - -You can see the gnarly reddish wall in the picture. That and a bunch of other grime and residue was cleaned off as part of a neighborhood exterior painting project. The place is looking a lot nicer now from the outside (no pictures of this I'm afraid). - -We also got some minor things done, like installing curtains to my fiancé's office room. I got some new toolboxes that I use to keep everything nice and organized, and hidden away when not needed. - -With all the materials and tools lying around our home has felt like a job site, but now that everything's done and cleaned up, I'm happy that everything got done as well as it did. - -## FreeBSD server - -I have been setting up a home server and writing a blog series about it. Check out parts [one](home-server-part-1) and [two](home-server-part-2)! My plan is to move things back from the cloud & other people's computers back to my home and self-host as many things as possible. - -## Books - -**Finished** - -- Robert Nystrom, _Crafting Interpreters_ -- Michael W. Lucas, _Absolute FreeBSD_ -- Michael W. Lucas, _FreeBSD Mastery: ZFS_ -- Michael W. Lucas, _FreeBSD Mastery: Jails_ -- Leo Brodie, _Thinking Forth_ - -**Started** - -- Benjamin C. Pierce, _Types and Programming Languages_ -- Adam Aleksic, _Algospeak_ - -## Games - -**Played** - -- Bomb Rush Cyberfunk -- Hollow Knight: Silksong - -**Started** - -- God of War: Ragnarök - -**Enjoyed as a VOD/live stream** - -- Watched CDawgVA's playthroughs of MGS 1–3 and Revengeance -- A lot of Silksong VODs and speedruns - -## Travel - -I late August I went on a company trip to Krakow, Poland for one weekend. A nice city with beautiful old town buildings, but sadly the weather was a bit dull. My favorite food spot was the [Black Duck](https://czarnakaczka.pl/) 😋. - -In September we had booked a trip to Italy with a group of friends. We stayed in Rome for a couple of days, enjoying the 30C heat and sights such as the Villa Borghese. We had already visited the Colosseum and the Vatican on earlier trips so we decided to avoid the queues and visit less popular places. It was the Jubileum year so many sights were jam-packed. After that we got a rental and drove ~500km south to Calabria, where we spent the next week at a really nice villa overlooking the sea. - -{{ fig(src="/files/villa.jpeg", alt="The view from the villa") }} - -Later in September I attended EuroBSDCon 2025 in Zagreb, Croatia. A lot of great talks and lab sessions! I might write a conference notes post about the trip. - -So, a lot of traveling in a short timespan, so I've been feeling quite exhausted. The rest of the autumn is looking less hectic, so I might be able to work on projects more 🤔 - -## Anime - -- Puella Magi Madoka Magica -- Started Kill la Kill -- Chainsaw Man (still a couple of eps to go) - -## Previous update - -- [☀️ Now, summer 2025](/posts/now-2025-summer) diff --git a/content/posts/now-2025-fall/index.md b/content/posts/now-2025-fall/index.md new file mode 100644 index 0000000..0c21cd6 --- /dev/null +++ b/content/posts/now-2025-fall/index.md @@ -0,0 +1,90 @@ +--- +title: 🍁 Now, fall 2025 +date: 2025-10-05 +description: Late summer and early autumn have been a time of ups and downs. +extra: + kind: now +--- + +Late summer and early autumn have been a time of ups and downs. + +I lost my father to illness. The passing was expected, in a way, so I have had time to process things in advance, but when it actually happened it turned everything on its head for a while. + +Sorting out things after the death has taken a lot of time and effort, but things are now more or less in order. I have got a lot of support and help from my peers, and I feel I have let myself process the grief well. I try to focus on positive things. + +We've also been planning the wedding that's taking place in 2026. In many ways I feel out of my element, but things are moving forward. + +{{ toc() }} + +## Home + +A lot of renovations this summer. One of the biggest ones was the translucent roofing installation: + +{{ fig(src="valokate_1.jpg", alt="Installing the sunroof") }} + +After around six hours (including breaks), it's all done! Just in time for rain. + +{{ fig(src="valokate_2.jpg", alt="Complete sunroof") }} + +You can see the gnarly reddish wall in the picture. That and a bunch of other grime and residue was cleaned off as part of a neighborhood exterior painting project. The place is looking a lot nicer now from the outside (no pictures of this I'm afraid). + +We also got some minor things done, like installing curtains to my fiancé's office room. I got some new toolboxes that I use to keep everything nice and organized, and hidden away when not needed. + +With all the materials and tools lying around our home has felt like a job site, but now that everything's done and cleaned up, I'm happy that everything got done as well as it did. + +## FreeBSD server + +I have been setting up a home server and writing a blog series about it. Check out parts [one](home-server-part-1) and [two](home-server-part-2)! My plan is to move things back from the cloud & other people's computers back to my home and self-host as many things as possible. + +## Books + +**Finished** + +- Robert Nystrom, _Crafting Interpreters_ +- Michael W. Lucas, _Absolute FreeBSD_ +- Michael W. Lucas, _FreeBSD Mastery: ZFS_ +- Michael W. Lucas, _FreeBSD Mastery: Jails_ +- Leo Brodie, _Thinking Forth_ + +**Started** + +- Benjamin C. Pierce, _Types and Programming Languages_ +- Adam Aleksic, _Algospeak_ + +## Games + +**Played** + +- Bomb Rush Cyberfunk +- Hollow Knight: Silksong + +**Started** + +- God of War: Ragnarök + +**Enjoyed as a VOD/live stream** + +- Watched CDawgVA's playthroughs of MGS 1–3 and Revengeance +- A lot of Silksong VODs and speedruns + +## Travel + +I late August I went on a company trip to Krakow, Poland for one weekend. A nice city with beautiful old town buildings, but sadly the weather was a bit dull. My favorite food spot was the [Black Duck](https://czarnakaczka.pl/) 😋. + +In September we had booked a trip to Italy with a group of friends. We stayed in Rome for a couple of days, enjoying the 30C heat and sights such as the Villa Borghese. We had already visited the Colosseum and the Vatican on earlier trips so we decided to avoid the queues and visit less popular places. It was the Jubileum year so many sights were jam-packed. After that we got a rental and drove ~500km south to Calabria, where we spent the next week at a really nice villa overlooking the sea. + +{{ fig(src="villa.jpeg", alt="The view from the villa") }} + +Later in September I attended EuroBSDCon 2025 in Zagreb, Croatia. A lot of great talks and lab sessions! I might write a conference notes post about the trip. + +So, a lot of traveling in a short timespan, so I've been feeling quite exhausted. The rest of the autumn is looking less hectic, so I might be able to work on projects more 🤔 + +## Anime + +- Puella Magi Madoka Magica +- Started Kill la Kill +- Chainsaw Man (still a couple of eps to go) + +## Previous update + +- [☀️ Now, summer 2025](/posts/now-2025-summer) diff --git a/content/posts/now-2025-fall/valokate_1.jpg b/content/posts/now-2025-fall/valokate_1.jpg new file mode 100644 index 0000000..1ddccf2 Binary files /dev/null and b/content/posts/now-2025-fall/valokate_1.jpg differ diff --git a/content/posts/now-2025-fall/valokate_2.jpg b/content/posts/now-2025-fall/valokate_2.jpg new file mode 100644 index 0000000..3d66088 Binary files /dev/null and b/content/posts/now-2025-fall/valokate_2.jpg differ diff --git a/content/posts/now-2025-fall/villa.jpeg b/content/posts/now-2025-fall/villa.jpeg new file mode 100644 index 0000000..51cc84b Binary files /dev/null and b/content/posts/now-2025-fall/villa.jpeg differ diff --git a/content/posts/now-2025-summer.md b/content/posts/now-2025-summer.md deleted file mode 100644 index 81293ca..0000000 --- a/content/posts/now-2025-summer.md +++ /dev/null @@ -1,114 +0,0 @@ ---- -title: ☀️ Now, summer 2025 -date: 2025-06-15 -description: | - Here's three seasons of updates in one package. -extra: - kind: now ---- - -{{ fig(src="/files/kenrokuen.jpg", alt="A torii gate in the Kenroku-en garden") }} - -Here's three seasons of updates in one package. - -{{ toc() }} - -## AutereDB project - -I was reading [Designing Data-intensive Applications](https://www.oreilly.com/library/view/designing-data-intensive-applications/9781491903063/) and I came across a chapter where the author goes over log-structured database architecture. I knew about B-tree based systems, but this seemingly simpler approach was new to me. I was intrigued, but I also felt like I didn't fully grok how every cog in the theoretical database machine worked together. - -Therefore, I decided to write a quick MVP thing in Rust during a conference flight. The result is **AutereDB** ([Github](https://github.com/jantuomi/autere_db)). - -AutereDB is more like a database engine. It doesn't have a query language, authorization mechanisms, multi-table support or other higher-level features. It simply makes sure that records are written to disk as expected, and queries return the correct data. - -You can read more about the project in the `README.md`. - -I have no plans to really further this project. I feel it was a one-off to better grok how these things work. I consider this goal met; I am now more confident about reasoning about data systems. Many things that I learned the hard way during this project have already proved to be beneficial in my day job, so mission success. - -Help yourself to the `ARCHITECTURE.md` file in the repository, and have a laugh at how I made multiple major miscalculations early on in the project, and then scrambled to fix these as I realized my folly. I tried to document most of these in the architecture decision log, for posterity. - -## FemtoQueue project - -Another data-centric project I conjured up: a Python task queue library that uses files in the filesystem for task persistence, and `rename()` to atomically move tasks from one state to another. - -FemtoQueue is on [Github](https://github.com/jantuomi/femtoqueue), as well as on [PyPI](https://pypi.org/project/femtoqueue/) as a pip-installable package. - -I'm pretty proud of this one. The core idea fits in under 100 lines of no-dependency Python. The current version with QoL stuff and docstrings clocks in at around 350 LoC. I could see it being very useful for small projects where you need some kind of a durable queue for sending emails etc., but don't want to add a whole separate networked Redis-like thing, or Celery or a home-cooked SQLite-based solution. - -## Japan trip - -Me and my fiancée made a long-awaited trip to Japan in the spring. We landed in the KIX airport near Ōsaka right in the middle of the cherry blossom season, hanami. - -We traveled the central island, Honshū, for three weeks. Mostly by train, but also by bus. We visited Ōsaka, Nara, Kyōto, Gifu-Takayama, Kanazawa and finally Tōkyō. This route skips the popular south-coast bullet train connection, instead going north through the mountains. - -{{ fig(src="/files/samurai_garden.jpeg", alt="The garden of a preserved samurai family home, with a koi fish pond and stone lantern") }} - -I got to finally test my Japanese. Speaking was very laborious at first, but it got way easier the more I got to practise. My vocabulary didn't improve that much, but what improved were things like _cadence_, _[aizuchi](https://en.wikipedia.org/wiki/Aizuchi)_, and ability to respond with more natural word choices. Big up to active listening and copying native speakers. - -We ate and drank well. We mostly gravitated towards traditional-style, or _washoku_, establishments. Our favorite meals were prepared by a sweet grandma in a hot springs guesthouse in the mountains. Simple and clean ingredients. - -Something to note: pickled veggies go great with meat-centered, hearty meals. We were eager to implement this in our home cooking as soon as we got home. - -{{ fig(src="/files/wagyu_grill.jpeg", alt="Wagyu beef, mushroom and peppers on a tabletop charcoal grill") }} - -We visited the obligatory tourist spots, like the _Thousand torī gates_ in Kyōto, and the _Dōtonbori shopping street_ in Ōsaka. We like seeing sights, but we know that our most memorable experiences have always come naturally from exploration and keeping an open mind. That's why we spent only a fraction of our time visiting Tripadvisor locations and most of the time wandering around, following local recommendations and keeping our eyes, ears and nostrils peeled. - -We are planning to travel there again, maybe on honeymoon. - -## FreeBSD home server - -I have recently started a project of setting up a small publicly accessible home server that will replace my DigitalOcean VPS in some time. The system will be very FreeBSD jail focused. I have just received the hardware and verified that the everything is up to par. Very hyped about this one. - -I am planning to write some posts about the progress. - -## Home - -A lot of small things need renovated. I guess this is what owning a house is. We have done a lot, and even more is still on the backlog. - -しょうがないね。 - -Going to install some patio roofing during the next couple of weeks, first time doing that. - -## Books - -Mostly FreeBSD stuff by [Michael W. Lucas](https://mwl.io/nonfiction/os). - -**Finished** - -- [Designing Data-intensive Applications](https://www.oreilly.com/library/view/designing-data-intensive-applications/9781491903063/) (took a long time to work through this one) -- FreeBSD Mastery: Storage Essentials - -**Started** - -- Absolute FreeBSD -- FreeBSD Mastery: ZFS -- FreeBSD Mastery: Jails - -## Games - -**Played** - -- Gato Roboto -- A Short Hike -- Cozy Grove (watched my fiancée play this one) -- Warhammer 40k: Boltgun - -**Enjoyed as a VOD/live stream** - -- Blue Prince -- Rainworld - -## Anime - -- Jujutsu Kaisen 0, S1, S2 -- FLCL Progressive -- Demon Slayer: Mugen Train -- Evangelion rebuild movies -- Blame! (movie) -- Puella Magi Madoka Magica -- Dandadan -- Chainsaw Man (still a couple of eps to go) - -## Previous update - -- [🍁 Now, fall 2024](/posts/now-2024-fall) diff --git a/content/posts/now-2025-summer/index.md b/content/posts/now-2025-summer/index.md new file mode 100644 index 0000000..d3bbaa5 --- /dev/null +++ b/content/posts/now-2025-summer/index.md @@ -0,0 +1,114 @@ +--- +title: ☀️ Now, summer 2025 +date: 2025-06-15 +description: | + Here's three seasons of updates in one package. +extra: + kind: now +--- + +{{ fig(src="kenrokuen.jpg", alt="A torii gate in the Kenroku-en garden") }} + +Here's three seasons of updates in one package. + +{{ toc() }} + +## AutereDB project + +I was reading [Designing Data-intensive Applications](https://www.oreilly.com/library/view/designing-data-intensive-applications/9781491903063/) and I came across a chapter where the author goes over log-structured database architecture. I knew about B-tree based systems, but this seemingly simpler approach was new to me. I was intrigued, but I also felt like I didn't fully grok how every cog in the theoretical database machine worked together. + +Therefore, I decided to write a quick MVP thing in Rust during a conference flight. The result is **AutereDB** ([Github](https://github.com/jantuomi/autere_db)). + +AutereDB is more like a database engine. It doesn't have a query language, authorization mechanisms, multi-table support or other higher-level features. It simply makes sure that records are written to disk as expected, and queries return the correct data. + +You can read more about the project in the `README.md`. + +I have no plans to really further this project. I feel it was a one-off to better grok how these things work. I consider this goal met; I am now more confident about reasoning about data systems. Many things that I learned the hard way during this project have already proved to be beneficial in my day job, so mission success. + +Help yourself to the `ARCHITECTURE.md` file in the repository, and have a laugh at how I made multiple major miscalculations early on in the project, and then scrambled to fix these as I realized my folly. I tried to document most of these in the architecture decision log, for posterity. + +## FemtoQueue project + +Another data-centric project I conjured up: a Python task queue library that uses files in the filesystem for task persistence, and `rename()` to atomically move tasks from one state to another. + +FemtoQueue is on [Github](https://github.com/jantuomi/femtoqueue), as well as on [PyPI](https://pypi.org/project/femtoqueue/) as a pip-installable package. + +I'm pretty proud of this one. The core idea fits in under 100 lines of no-dependency Python. The current version with QoL stuff and docstrings clocks in at around 350 LoC. I could see it being very useful for small projects where you need some kind of a durable queue for sending emails etc., but don't want to add a whole separate networked Redis-like thing, or Celery or a home-cooked SQLite-based solution. + +## Japan trip + +Me and my fiancée made a long-awaited trip to Japan in the spring. We landed in the KIX airport near Ōsaka right in the middle of the cherry blossom season, hanami. + +We traveled the central island, Honshū, for three weeks. Mostly by train, but also by bus. We visited Ōsaka, Nara, Kyōto, Gifu-Takayama, Kanazawa and finally Tōkyō. This route skips the popular south-coast bullet train connection, instead going north through the mountains. + +{{ fig(src="samurai_garden.jpeg", alt="The garden of a preserved samurai family home, with a koi fish pond and stone lantern") }} + +I got to finally test my Japanese. Speaking was very laborious at first, but it got way easier the more I got to practise. My vocabulary didn't improve that much, but what improved were things like _cadence_, _[aizuchi](https://en.wikipedia.org/wiki/Aizuchi)_, and ability to respond with more natural word choices. Big up to active listening and copying native speakers. + +We ate and drank well. We mostly gravitated towards traditional-style, or _washoku_, establishments. Our favorite meals were prepared by a sweet grandma in a hot springs guesthouse in the mountains. Simple and clean ingredients. + +Something to note: pickled veggies go great with meat-centered, hearty meals. We were eager to implement this in our home cooking as soon as we got home. + +{{ fig(src="wagyu_grill.jpeg", alt="Wagyu beef, mushroom and peppers on a tabletop charcoal grill") }} + +We visited the obligatory tourist spots, like the _Thousand torī gates_ in Kyōto, and the _Dōtonbori shopping street_ in Ōsaka. We like seeing sights, but we know that our most memorable experiences have always come naturally from exploration and keeping an open mind. That's why we spent only a fraction of our time visiting Tripadvisor locations and most of the time wandering around, following local recommendations and keeping our eyes, ears and nostrils peeled. + +We are planning to travel there again, maybe on honeymoon. + +## FreeBSD home server + +I have recently started a project of setting up a small publicly accessible home server that will replace my DigitalOcean VPS in some time. The system will be very FreeBSD jail focused. I have just received the hardware and verified that the everything is up to par. Very hyped about this one. + +I am planning to write some posts about the progress. + +## Home + +A lot of small things need renovated. I guess this is what owning a house is. We have done a lot, and even more is still on the backlog. + +しょうがないね。 + +Going to install some patio roofing during the next couple of weeks, first time doing that. + +## Books + +Mostly FreeBSD stuff by [Michael W. Lucas](https://mwl.io/nonfiction/os). + +**Finished** + +- [Designing Data-intensive Applications](https://www.oreilly.com/library/view/designing-data-intensive-applications/9781491903063/) (took a long time to work through this one) +- FreeBSD Mastery: Storage Essentials + +**Started** + +- Absolute FreeBSD +- FreeBSD Mastery: ZFS +- FreeBSD Mastery: Jails + +## Games + +**Played** + +- Gato Roboto +- A Short Hike +- Cozy Grove (watched my fiancée play this one) +- Warhammer 40k: Boltgun + +**Enjoyed as a VOD/live stream** + +- Blue Prince +- Rainworld + +## Anime + +- Jujutsu Kaisen 0, S1, S2 +- FLCL Progressive +- Demon Slayer: Mugen Train +- Evangelion rebuild movies +- Blame! (movie) +- Puella Magi Madoka Magica +- Dandadan +- Chainsaw Man (still a couple of eps to go) + +## Previous update + +- [🍁 Now, fall 2024](/posts/now-2024-fall) diff --git a/content/posts/now-2025-summer/kenrokuen.jpg b/content/posts/now-2025-summer/kenrokuen.jpg new file mode 100644 index 0000000..f739d82 Binary files /dev/null and b/content/posts/now-2025-summer/kenrokuen.jpg differ diff --git a/content/posts/now-2025-summer/samurai_garden.jpeg b/content/posts/now-2025-summer/samurai_garden.jpeg new file mode 100644 index 0000000..f6637b0 Binary files /dev/null and b/content/posts/now-2025-summer/samurai_garden.jpeg differ diff --git a/content/posts/now-2025-summer/wagyu_grill.jpeg b/content/posts/now-2025-summer/wagyu_grill.jpeg new file mode 100644 index 0000000..0e835bc Binary files /dev/null and b/content/posts/now-2025-summer/wagyu_grill.jpeg differ diff --git a/content/posts/now-2025-winter.md b/content/posts/now-2025-winter.md deleted file mode 100644 index 0462760..0000000 --- a/content/posts/now-2025-winter.md +++ /dev/null @@ -1,129 +0,0 @@ ---- -title: ❄️ Now, winter 2025 -date: 2025-12-16 -description: | - Fall has gone by, fast. Mostly rain and darkness. Managing the estate of my late father has taken up a lot of time I would've otherwise spent on hobbies. -extra: - kind: now ---- - -Fall has gone by, fast. Mostly rain and darkness. - -Managing the estate of my late father has taken up a lot of time I would've otherwise spent on hobbies. - -Some decor and reno at home, some game programming, a lot of games. Game design has been on my mind a lot lately. - -I also attended my first ever Finnish defence forces reservist refresher training exercise. I had a good time! - -{{ toc() }} - -## Metroidvania game dev - -🚨 Original game idea alert 🚨 - -I decided to make a 2D platformer with ability-gated progression. It's gonna have pixel art too. - -It's been a very long time since I made a complete game project. I don't care about how many players I'm gonna get. I just want to create a game, for me. - -I figured I should do some proper design upfront instead of doing the easy thing, i.e. jumping straight into programming mechanics. That's why I started writing a Game Design Document ASAP. With that, maybe I can come back to this project easier, after the inevitable plateau and loss of interest. - -Maybe some screenshots later. - -## Future of ATK16 - -An idea came to me in my sleep. I want to change the course of my computing project ATK16 and turn it into a stack-based, Forth-like system. - -Clearly I've been subconsciously processing ideas from Forth and [Uxntal](https://wiki.xxiivv.com/site/uxntal.html), since my current vision is a hodgepodge of features from those two: - -- an interactive REPL-compiler, able to compile native code on the fly -- a system-wide dictionary (map of symbols to code or data), giving structure to the linear memory -- reverse Polish notation, which falls naturally out of the stack machine design - -A basic session could look like this: - -```sh -ATK16 v0.1 -Dictionary usage: 10 KB - -: average3 (a b c -- avg) - + + 3 / ; -``` - -This would define a new symbol, `average3`, that computes the average of the top 3 elements on the stack and pushes the result on the stack. The stack could be implemented in hardware with a top-of-stack optimization: the top three slots live in fast registers, while the rest is in dedicated SRAM. This would allow fast access to the most important, i.e. topmost, elements of the stack. - -In addition, I'm thinking of doing some things a bit unorthodox: - -- The system dictionary could live in FRAM, which is non-volatile. All symbol definitions would then be automatically persistent, like files on a disk. This could be huge. -- A programmer board could then allow me to directly update this dictionary from my dev laptop, possibly by emulating the same REPL software that runs on the hardware. - -In fact, it might be possible to design this in a way that completely does away with assemblers and cross-compilers. I would just need an emulator and a bit of bootstrap assembly that implements a minimal REPL that can self-compile new symbols into the dictionary. Like this, I wouldn't need to worry about ensuring that the assembler and self-compiler work exactly the same: there is only one compiler implementation! I just emulate it on my laptop. Everything is implementation defined! - -## Home - -### ”Dopamine office” decor - -Office room number one is now pretty much complete. We tried to combine some of pastel colors and fun patterns, while keeping it smart and tidy. - -{{ fig(src="/files/dopamine_office.jpeg", alt="The dopamine office") }} - -### Backdoor clothes rack and wall feature - -We use our apartment's back door a lot, more than the front door really. It's easier to take the dog out that way. - -Until now, there has been nowhere to really hang our outdoor clothes nicely. The dining table and its chairs have doubled as a clothing rack (not ideal). - -So we got the idea to repurpose the empty space in the corner, adding some visual interest with acoustic paneling as well as utility with some colorful "knobs" that you can hang coats and scarves on. - -{{ fig(src="/files/backdoor_feature_wip.jpeg", alt="Install in progress") }} - -And the final product (in completely different lighting): - -{{ fig(src="/files/backdoor_feature_done.jpeg", alt="An installed wood paneling with some coats hanging off of colored knobs") }} - -The white box is an IKEA shoebox. We keep some dog gear in there. - -### Cool dog print - -{{ fig(src="/files/taulu_koira.jpeg", alt="Cool dog") }} - -## Games - -**Played** - -- God of War: Ragnarök -- Cocoon -- Another Crab’s Treasure -- Super Metroid -- Metroid Dread -- Prince of Persia: The Lost Crown -- Ori and the Blind Forest - -## Anime - -**Finished** - -- Bocchi the Rock S1 -- Spirited Away (movie) -- Ghost in the Shell (movie) -- Kill la Kill - -**Started** - -- Dandadan S2 -- Spy x Family S1 - -## Tunes - -Mostly [Moomin music](https://youtu.be/Bf1pR4rkPK8). I went to see the _Muumimusiikkia_ show in Musiikkitalo (Helsinki) in November, and after that I haven't been able to get Shiratori's melodies out of my head. This is a positive problem. - -{{ fig(src="/files/muumimusiikkia.jpeg", alt="A photo from our seats at Musiikkitalo, showing the brochure") }} - -### Spotify Wrapped - -I feel like I'm using Spotify less and less. Might drop the subscription in the near future. I really don't like the policies they are driving. - -{{ fig(src="/files/2025_wrapped_genres.jpeg", alt="My top genres, all over the place") }} - -## Previous update - -- [🍁 Now, fall 2025](/posts/now-2025-fall) diff --git a/content/posts/now-2025-winter/2025_wrapped_genres.jpeg b/content/posts/now-2025-winter/2025_wrapped_genres.jpeg new file mode 100644 index 0000000..630be8a Binary files /dev/null and b/content/posts/now-2025-winter/2025_wrapped_genres.jpeg differ diff --git a/content/posts/now-2025-winter/backdoor_feature_done.jpeg b/content/posts/now-2025-winter/backdoor_feature_done.jpeg new file mode 100644 index 0000000..a9c71d5 Binary files /dev/null and b/content/posts/now-2025-winter/backdoor_feature_done.jpeg differ diff --git a/content/posts/now-2025-winter/backdoor_feature_wip.jpeg b/content/posts/now-2025-winter/backdoor_feature_wip.jpeg new file mode 100644 index 0000000..2f381d1 Binary files /dev/null and b/content/posts/now-2025-winter/backdoor_feature_wip.jpeg differ diff --git a/content/posts/now-2025-winter/dopamine_office.jpeg b/content/posts/now-2025-winter/dopamine_office.jpeg new file mode 100644 index 0000000..aad4e65 Binary files /dev/null and b/content/posts/now-2025-winter/dopamine_office.jpeg differ diff --git a/content/posts/now-2025-winter/index.md b/content/posts/now-2025-winter/index.md new file mode 100644 index 0000000..2fae6f8 --- /dev/null +++ b/content/posts/now-2025-winter/index.md @@ -0,0 +1,129 @@ +--- +title: ❄️ Now, winter 2025 +date: 2025-12-16 +description: | + Fall has gone by, fast. Mostly rain and darkness. Managing the estate of my late father has taken up a lot of time I would've otherwise spent on hobbies. +extra: + kind: now +--- + +Fall has gone by, fast. Mostly rain and darkness. + +Managing the estate of my late father has taken up a lot of time I would've otherwise spent on hobbies. + +Some decor and reno at home, some game programming, a lot of games. Game design has been on my mind a lot lately. + +I also attended my first ever Finnish defence forces reservist refresher training exercise. I had a good time! + +{{ toc() }} + +## Metroidvania game dev + +🚨 Original game idea alert 🚨 + +I decided to make a 2D platformer with ability-gated progression. It's gonna have pixel art too. + +It's been a very long time since I made a complete game project. I don't care about how many players I'm gonna get. I just want to create a game, for me. + +I figured I should do some proper design upfront instead of doing the easy thing, i.e. jumping straight into programming mechanics. That's why I started writing a Game Design Document ASAP. With that, maybe I can come back to this project easier, after the inevitable plateau and loss of interest. + +Maybe some screenshots later. + +## Future of ATK16 + +An idea came to me in my sleep. I want to change the course of my computing project ATK16 and turn it into a stack-based, Forth-like system. + +Clearly I've been subconsciously processing ideas from Forth and [Uxntal](https://wiki.xxiivv.com/site/uxntal.html), since my current vision is a hodgepodge of features from those two: + +- an interactive REPL-compiler, able to compile native code on the fly +- a system-wide dictionary (map of symbols to code or data), giving structure to the linear memory +- reverse Polish notation, which falls naturally out of the stack machine design + +A basic session could look like this: + +```sh +ATK16 v0.1 +Dictionary usage: 10 KB + +: average3 (a b c -- avg) + + + 3 / ; +``` + +This would define a new symbol, `average3`, that computes the average of the top 3 elements on the stack and pushes the result on the stack. The stack could be implemented in hardware with a top-of-stack optimization: the top three slots live in fast registers, while the rest is in dedicated SRAM. This would allow fast access to the most important, i.e. topmost, elements of the stack. + +In addition, I'm thinking of doing some things a bit unorthodox: + +- The system dictionary could live in FRAM, which is non-volatile. All symbol definitions would then be automatically persistent, like files on a disk. This could be huge. +- A programmer board could then allow me to directly update this dictionary from my dev laptop, possibly by emulating the same REPL software that runs on the hardware. + +In fact, it might be possible to design this in a way that completely does away with assemblers and cross-compilers. I would just need an emulator and a bit of bootstrap assembly that implements a minimal REPL that can self-compile new symbols into the dictionary. Like this, I wouldn't need to worry about ensuring that the assembler and self-compiler work exactly the same: there is only one compiler implementation! I just emulate it on my laptop. Everything is implementation defined! + +## Home + +### ”Dopamine office” decor + +Office room number one is now pretty much complete. We tried to combine some of pastel colors and fun patterns, while keeping it smart and tidy. + +{{ fig(src="dopamine_office.jpeg", alt="The dopamine office") }} + +### Backdoor clothes rack and wall feature + +We use our apartment's back door a lot, more than the front door really. It's easier to take the dog out that way. + +Until now, there has been nowhere to really hang our outdoor clothes nicely. The dining table and its chairs have doubled as a clothing rack (not ideal). + +So we got the idea to repurpose the empty space in the corner, adding some visual interest with acoustic paneling as well as utility with some colorful "knobs" that you can hang coats and scarves on. + +{{ fig(src="backdoor_feature_wip.jpeg", alt="Install in progress") }} + +And the final product (in completely different lighting): + +{{ fig(src="backdoor_feature_done.jpeg", alt="An installed wood paneling with some coats hanging off of colored knobs") }} + +The white box is an IKEA shoebox. We keep some dog gear in there. + +### Cool dog print + +{{ fig(src="taulu_koira.jpeg", alt="Cool dog") }} + +## Games + +**Played** + +- God of War: Ragnarök +- Cocoon +- Another Crab’s Treasure +- Super Metroid +- Metroid Dread +- Prince of Persia: The Lost Crown +- Ori and the Blind Forest + +## Anime + +**Finished** + +- Bocchi the Rock S1 +- Spirited Away (movie) +- Ghost in the Shell (movie) +- Kill la Kill + +**Started** + +- Dandadan S2 +- Spy x Family S1 + +## Tunes + +Mostly [Moomin music](https://youtu.be/Bf1pR4rkPK8). I went to see the _Muumimusiikkia_ show in Musiikkitalo (Helsinki) in November, and after that I haven't been able to get Shiratori's melodies out of my head. This is a positive problem. + +{{ fig(src="muumimusiikkia.jpeg", alt="A photo from our seats at Musiikkitalo, showing the brochure") }} + +### Spotify Wrapped + +I feel like I'm using Spotify less and less. Might drop the subscription in the near future. I really don't like the policies they are driving. + +{{ fig(src="2025_wrapped_genres.jpeg", alt="My top genres, all over the place") }} + +## Previous update + +- [🍁 Now, fall 2025](/posts/now-2025-fall) diff --git a/content/posts/now-2025-winter/muumimusiikkia.jpeg b/content/posts/now-2025-winter/muumimusiikkia.jpeg new file mode 100644 index 0000000..a99d8f6 Binary files /dev/null and b/content/posts/now-2025-winter/muumimusiikkia.jpeg differ diff --git a/content/posts/now-2025-winter/taulu_koira.jpeg b/content/posts/now-2025-winter/taulu_koira.jpeg new file mode 100644 index 0000000..11c4f32 Binary files /dev/null and b/content/posts/now-2025-winter/taulu_koira.jpeg differ diff --git a/content/posts/qcon-london-2024-takeaways.md b/content/posts/qcon-london-2024-takeaways.md deleted file mode 100644 index 17d8b3b..0000000 --- a/content/posts/qcon-london-2024-takeaways.md +++ /dev/null @@ -1,112 +0,0 @@ ---- -title: QCon London 2024 takeaways -date: 2024-04-20 -extra: - kind: post ---- - -QCon London 2024 was a multidisciplinary software engineering conference in the London QEII Centre in April 2024. I attended with the intention to learn about what's going on in the software-sphere. Especially in terms of software architecture. - -This was also my first time in the UK so I had to do some touristing. The weather was a bit chilly, half-cloudy with some intermittent drizzle. - -{{ fig(src="/files/london-from-above.jpg", alt="London from above in the late evening") }} - -## Common themes - -### Data products - -A term from data mesh architecture. Think of the data you produce as a product. A product has an audience and marketing. - -### Platform thinking, platform teams - -Enable your software teams by building a platform of supported programming languages/runtimes, CI/CD pipeline configurations, deployment clusters, etc. so that each team does not have to spend resources reinventing the wheel. - -Provide a _golden path_ (tried and tested technologies supported by the platform team) for new projects but do not smother your engineers' freedom of choice. - -### “GenAI/LLMs are not necessarily that useful” - -Probably comes as a surprise but the latest wave of AI hype isn't very substantiated. Issues of security, trust, quality, ownership and licensing. Keep a human in the loop. - -### Safe, performant and ecological - -Running software causes carbon emissions. Choose runtimes, programming languages and hosting options that minimize idle and active compute use. - -Avoid entire categories of vulnerabilities by picking safe languages such as Rust. - -## Most inspiring talks - -### **The Home Computer That Roared: How the BBC Micro Shaped Our World** by _Jeremy Ruston_ - -A deep history dive to the advent of home computing in the UK. Jeremy was involved in building the educational BBC Micro computer as well as TV children's show animations and Doctor Who games. Super inspiring stuff, especially since I can relate to many of the constraints Jeremy et al. faced since I'm working on my very constrained "ATK16" hobby CPU project myself. - -### **Thinking like an architect** by _Gregor Hohpe (AWS)_ - -Architects find connections. Between perspectives, between technologies, between people. - -The director's budgets, risks and customer success are connected to the engineer's refactorings, scalability and backups. - -Architects are an IQ multiplier, not the smartest person in the room. Architects make other people smarter. - -### **Architecting for Data Products** by _Danilo Sato (Thoughtworks)_ - -Move from _left-to-right_ architectures (operational ↣ analytics) to a data mesh. - -Make your data discoverable and usable via different protocols and formats. Make it self-serve (platform thinking). - -> 🙋🏼 Note: I didn't know much about data mesh literature before this talk. The most useful things I got from it were the words and terms to look up, such as the _DATSIS principles_, and the books to read (see [Reading list](#reading-list)). - -### **Building Your First Platform Team** in a Fast Growing Startup by _Jessica Andersson (Kognic)_ - -The spirit of DevOps has in part transmogrified into the idea of _software platforms_. DevOps as a term has degraded to just mean Ops in many places. -Empower product teams with an explicit platform. Each company has a platform, implicit or explicit. -A _base plaform_ should provide: - -- **CI/CD**: pipeline templates, runner images, best practices -- **Runtime**: programming languages and runtimes, but also container runtimes, orchestration -- **Observability**: distributed tracing, logging, monitoring, alerting - -## Reading list {#reading-list} - -- ⭐️ [Team topologies](https://teamtopologies.com/book) by _Matthew Skelton and Manuel Pais_ -- [Domain-Driven Design](https://www.amazon.com/Domain-Driven-Design-Tackling-Complexity-Software/dp/0321125215) by _Eric Evans_ -- [Data Mesh](https://www.oreilly.com/library/view/data-mesh/9781492092384/) by _Zhamak Dehghani_ -- [Architect Elevator](https://architectelevator.com/) by _Gregor Hohpe_ - - all the other books by Hohpe also - -## Word dump - -I did not recognize these words or concepts. - -- attestation testing, SLSA -- server-driven UI -- DATSIS principles -- data vault architecture -- source-aligned, consumer-aligned data products -- galaxy apps -- 2-step commit -- wardley map -- BPMN (business process model and notation) -- eBPF -- expand-contract pattern -- SCI (software carbon intensity) -- WASI (WebAssembly System Interface) -- Gartner hype cycle -- trust boundary (in context of LLMs) -- LLM agent -- shift left (in context of software platforms) - -## Food and drink - -### Fantastic Indian cuisine: [Dishoom Covent Garden](https://www.dishoom.com/covent-garden/) - -You don't get Indian food like this in Finland. Super flavourful, suitably spicy. Was handed a gratis chai to drink while queuing up in the drizzling rain too. Excellent customer service. - -### Real Ale Pub: [The Harp](https://www.harpcoventgarden.com/) - -Nothing mindblowing, just good ale and a comfy vibe. A good place to sit out the rain. - -### Bloody lovely: [Duke of Argyll](https://maps.app.goo.gl/21YHi1ZBrXohhczj9) - -I had to eat fish & chips at least once just to check it off my bucket list. I don't know if theirs is the best or even proper, but I enjoyed it a lot. - -{{ fig(src="/files/fish-and-chips-duke-of-argyll.jpg", alt="Fish and chips at the Duke of Argyll") }} diff --git a/content/posts/qcon-london-2024-takeaways/fish-and-chips-duke-of-argyll.jpg b/content/posts/qcon-london-2024-takeaways/fish-and-chips-duke-of-argyll.jpg new file mode 100644 index 0000000..a9fefc8 Binary files /dev/null and b/content/posts/qcon-london-2024-takeaways/fish-and-chips-duke-of-argyll.jpg differ diff --git a/content/posts/qcon-london-2024-takeaways/index.md b/content/posts/qcon-london-2024-takeaways/index.md new file mode 100644 index 0000000..4c644e7 --- /dev/null +++ b/content/posts/qcon-london-2024-takeaways/index.md @@ -0,0 +1,112 @@ +--- +title: QCon London 2024 takeaways +date: 2024-04-20 +extra: + kind: post +--- + +QCon London 2024 was a multidisciplinary software engineering conference in the London QEII Centre in April 2024. I attended with the intention to learn about what's going on in the software-sphere. Especially in terms of software architecture. + +This was also my first time in the UK so I had to do some touristing. The weather was a bit chilly, half-cloudy with some intermittent drizzle. + +{{ fig(src="london-from-above.jpg", alt="London from above in the late evening") }} + +## Common themes + +### Data products + +A term from data mesh architecture. Think of the data you produce as a product. A product has an audience and marketing. + +### Platform thinking, platform teams + +Enable your software teams by building a platform of supported programming languages/runtimes, CI/CD pipeline configurations, deployment clusters, etc. so that each team does not have to spend resources reinventing the wheel. + +Provide a _golden path_ (tried and tested technologies supported by the platform team) for new projects but do not smother your engineers' freedom of choice. + +### “GenAI/LLMs are not necessarily that useful” + +Probably comes as a surprise but the latest wave of AI hype isn't very substantiated. Issues of security, trust, quality, ownership and licensing. Keep a human in the loop. + +### Safe, performant and ecological + +Running software causes carbon emissions. Choose runtimes, programming languages and hosting options that minimize idle and active compute use. + +Avoid entire categories of vulnerabilities by picking safe languages such as Rust. + +## Most inspiring talks + +### **The Home Computer That Roared: How the BBC Micro Shaped Our World** by _Jeremy Ruston_ + +A deep history dive to the advent of home computing in the UK. Jeremy was involved in building the educational BBC Micro computer as well as TV children's show animations and Doctor Who games. Super inspiring stuff, especially since I can relate to many of the constraints Jeremy et al. faced since I'm working on my very constrained "ATK16" hobby CPU project myself. + +### **Thinking like an architect** by _Gregor Hohpe (AWS)_ + +Architects find connections. Between perspectives, between technologies, between people. + +The director's budgets, risks and customer success are connected to the engineer's refactorings, scalability and backups. + +Architects are an IQ multiplier, not the smartest person in the room. Architects make other people smarter. + +### **Architecting for Data Products** by _Danilo Sato (Thoughtworks)_ + +Move from _left-to-right_ architectures (operational ↣ analytics) to a data mesh. + +Make your data discoverable and usable via different protocols and formats. Make it self-serve (platform thinking). + +> 🙋🏼 Note: I didn't know much about data mesh literature before this talk. The most useful things I got from it were the words and terms to look up, such as the _DATSIS principles_, and the books to read (see [Reading list](#reading-list)). + +### **Building Your First Platform Team** in a Fast Growing Startup by _Jessica Andersson (Kognic)_ + +The spirit of DevOps has in part transmogrified into the idea of _software platforms_. DevOps as a term has degraded to just mean Ops in many places. +Empower product teams with an explicit platform. Each company has a platform, implicit or explicit. +A _base plaform_ should provide: + +- **CI/CD**: pipeline templates, runner images, best practices +- **Runtime**: programming languages and runtimes, but also container runtimes, orchestration +- **Observability**: distributed tracing, logging, monitoring, alerting + +## Reading list {#reading-list} + +- ⭐️ [Team topologies](https://teamtopologies.com/book) by _Matthew Skelton and Manuel Pais_ +- [Domain-Driven Design](https://www.amazon.com/Domain-Driven-Design-Tackling-Complexity-Software/dp/0321125215) by _Eric Evans_ +- [Data Mesh](https://www.oreilly.com/library/view/data-mesh/9781492092384/) by _Zhamak Dehghani_ +- [Architect Elevator](https://architectelevator.com/) by _Gregor Hohpe_ + - all the other books by Hohpe also + +## Word dump + +I did not recognize these words or concepts. + +- attestation testing, SLSA +- server-driven UI +- DATSIS principles +- data vault architecture +- source-aligned, consumer-aligned data products +- galaxy apps +- 2-step commit +- wardley map +- BPMN (business process model and notation) +- eBPF +- expand-contract pattern +- SCI (software carbon intensity) +- WASI (WebAssembly System Interface) +- Gartner hype cycle +- trust boundary (in context of LLMs) +- LLM agent +- shift left (in context of software platforms) + +## Food and drink + +### Fantastic Indian cuisine: [Dishoom Covent Garden](https://www.dishoom.com/covent-garden/) + +You don't get Indian food like this in Finland. Super flavourful, suitably spicy. Was handed a gratis chai to drink while queuing up in the drizzling rain too. Excellent customer service. + +### Real Ale Pub: [The Harp](https://www.harpcoventgarden.com/) + +Nothing mindblowing, just good ale and a comfy vibe. A good place to sit out the rain. + +### Bloody lovely: [Duke of Argyll](https://maps.app.goo.gl/21YHi1ZBrXohhczj9) + +I had to eat fish & chips at least once just to check it off my bucket list. I don't know if theirs is the best or even proper, but I enjoyed it a lot. + +{{ fig(src="fish-and-chips-duke-of-argyll.jpg", alt="Fish and chips at the Duke of Argyll") }} diff --git a/content/posts/qcon-london-2024-takeaways/london-from-above.jpg b/content/posts/qcon-london-2024-takeaways/london-from-above.jpg new file mode 100644 index 0000000..a7762ed Binary files /dev/null and b/content/posts/qcon-london-2024-takeaways/london-from-above.jpg differ diff --git a/content/posts/subjective-review-miru.md b/content/posts/subjective-review-miru.md deleted file mode 100644 index d728a45..0000000 --- a/content/posts/subjective-review-miru.md +++ /dev/null @@ -1,73 +0,0 @@ ---- -title: "Subjective review: Miru" -date: 2024-07-14 -extra: - kind: note ---- - -{{ fig(src="/files/miru_time_to_kill_triple.jpg", alt="A graphic from the first pages of the Miru zine with the text ”Time to kill a god” overlaid on an imposing four-eyed figure.") }} - -## TL;DR - -It’s my favorite solo game, but it has some pacing problems. 8/10. - -## Miru, the solo analog adventure - -> 🎲 **Intended audience**: people who are familiar with basic tabletop role-playing game concepts. - -[**Miru**](https://hinokodo.itch.io/miru-an-analog-adventure-game) is a journaling solo RPG adventure by _Hinokodo_. - -Solo RPGs are pen-and-paper games that require only one player to play. Whereas most tabletop games require one player to take up the role of the game master or referee, solo RPGs replace this role with various mechanisms, such as an _oracle_ or a system of _random event tables_, paired with an increased player responsibility over the progression of the game. What would be ruled by the referee in a non-solo TTRPG, is resolved instead with a roll of the dice, or some other, potentially more mechanically curious procedure. - -Many solo games, Miru included, tell a single curated story. The focus can be very narrow: the game might take place in a specific location at a specific time, for instance. Miru does not allow you to create or customize your character either, which is par for the course. Solo systems are usually not generic enough to support arbitrary settings or adventure prompts. That is one of their strengths; the system can be tightly intertwined with the story in a way that would be inconvenient or even impossible in non-solo systems. - -Solo games are mechanically diverse and often involve innovative approaches to both visuals and game design. They also tend to be rather lightweight, including only the necessary rules or content in the often somewhat slim zine. Players often have to massage their brains a bit and deduce how some properties of the game interact. This is part of the appeal, for me at least. - -> I enjoy [The Soloist](https://soloist.substack.com/) and [Dan's News](https://danews.substack.com/) for my solo RPG news. - -_Journaling_ solo games are solo games that involve writing a journal of some kind as you progress. Miru asks you to write down a short rundown of what happened on each day of your adventure. You also draw the adventure map, hex by hex, on a blank map template. - -With definitions out of the way, let me start by saying that _Miru is the best solo adventure I have played_. The creative setting, the mechanics and distinctive visual identity come together to create a cohesive, well designed experience that I can wholeheartedly recommend. However, it is not perfect. There is a bit of a pacing issue during mid-game as well as some confusing or contradictory rules that are either explained by looking up the author's comments on the [itch.io](https://hinokodo.itch.io/miru-an-analog-adventure-game) page or not explained at all. These are minor issues, though, and can be circumvented with some _GM's fiat_ (which is of course also _player's fiat_ when you're flying solo). - -## First look - -{{ fig(src="/files/miru_gameplay.jpeg", alt="The Miru zine, polyhedral dice, various stationery and other items strewn across a dining table.") }} - -Miru paints a picture of a dystopian, post-metropolitan society future where humans begrudgingly coexist with humanoid and animal-like robots. The story is kicked off in a scene where a farm worker robot kills a relative of the main character in an act of self defense when it evaluates their drunken hooliganism to be a threat. Stricken, the main character vows revenge and sets off to find out the truth about the machines’ origins. - -The game is a procedurally generated hex crawl with apocalyptic gear-scavenging mechanisms and straightforward combat with a dash of god-slaying thrown in for good measure. - -Hexes are generated by combining 3 oracle tables: - -> **_Hex type_** (mountain, grassland, swamp...) → **_Event type_** → **_Particular event_** - -Visiting a single hex takes one in-game day. The main gameplay loop is about generating a new hex, surviving the generated event, and planning your next move such that you don't lose too many resources, such as food or sleep. Near the endgame, this survival aspect turns into loadout optimization when you prepare for the final boss. - -There is also currency management. Money, or **bitliths** (a snarky reference to cryptocurrency), are used to purchase survival resources as well as equipment from villages. The available stock improves as you discover more villages: the first village has level 1 stock, the second one level 2, and all the following ones level 3. - -## My experience - -The beginning was excellent. I quickly [grokked](https://www.merriam-webster.com/dictionary/grok) the rules and the objective of the game. The first hexes were significant; you could never know if you're going to get important gear, or nearly die to a robot-wolf. The plot was nicely propelled forwards by special events that happened on predetermined dates, and character progression felt good. These events, as well as random events dropped little bits of lore here and there. As a big fan of From Software games and figuring out the world by reading item descriptions, this kind of soft worldbuilding hit the nail on the head. - -This well thought-out and balanced part of the game lasted maybe 10 to 20 in-game days. - -My entire playthrough lasted 41 days. In wall clock time, it lasted me over three of months. There wasn’t actually enough much gameplay to warrant multiple months of play; I just had an extended break near the near-endgame part. I felt that the pacing there was a bit off, every hex felt very same-y. I had obtained enough resources and gear for exploring to be very safe and so the random events began to mean less and less. Little by little, the grinding-adjacent preparation for the final boss fight started to feel repetitive, and so I inadvertently took a break from the game. - -I returned to the game just recently and decided that I'm just going to have a try at the boss. Screw getting the best gear, I will finish the game! This was a good call, since the battle was intense. I threw everything at my disposal at my opponent and was _just_ strong enough to defeat it with one (1) HP remaining! Talk about a close call. - -I will not spoil the final boss or the ending here, but I want to say a bit about it nevertheless. Depending on whether or not you picked up on the [solarpunky](https://en.wikipedia.org/wiki/Solarpunk) undertones of the game during your playthrough, the ending may come either as a bit of a surprise. It surprised me at least. - -There's also something that seems to be a hook for the next game in the series, [Miru 2](https://hinokodo.itch.io/miru-ii-an-analog-horror-game), subtitled "Analog Horror Game". The third installation, [Miru 3](https://www.kickstarter.com/projects/mimicpublishing/miru-3-an-analog-defense-game), is an "Analog Defense Game". I am left wondering what happens in the plot between parts two and three. - -I give Miru a solid 8 out of 10. The visuals, the theme, the mechanics, and the overall vibe are all excellent. The grindy bit in the middle was really the only thing that reduced my enjoyment somewhat. Note however that your mileage may vary. Everything is determined by a roll of the dice. - -## Mixed media addendum - -I love mixed media art. Combining disciplines and breaking traditional boundaries makes great art. - -With that in mind, the designer has released an official soundtrack called [Static Abyss](https://hinokodo.itch.io/static-abyss) for the Miru game series. The physical minidisk release of the soundtrack comes with some kind of game rules too, based on photos that I've seen. Sadly, it has sold out, possibly soon after its release in April 2023. The digital version is available on the itch.io page. - -The concept of a soundtrack for a physical game is badass. I own one physical release of such a thing, the Mörk Borg adventure [Putrescence Regnant](https://jnohr.itch.io/putrescence-regnant), which takes the form of a highly stylized LP disc and sleeve. Very cool. - -{{ fig(src="/files/hinokodo-static-abyss.png", alt="A photo of the Static Abyss minidisk in its enclosure") }} -Photo attribution: [Hinokodo](https://hinokodo.itch.io/static-abyss) diff --git a/content/posts/subjective-review-miru/hinokodo-static-abyss.png b/content/posts/subjective-review-miru/hinokodo-static-abyss.png new file mode 100644 index 0000000..edfac67 Binary files /dev/null and b/content/posts/subjective-review-miru/hinokodo-static-abyss.png differ diff --git a/content/posts/subjective-review-miru/index.md b/content/posts/subjective-review-miru/index.md new file mode 100644 index 0000000..59ec7e4 --- /dev/null +++ b/content/posts/subjective-review-miru/index.md @@ -0,0 +1,73 @@ +--- +title: "Subjective review: Miru" +date: 2024-07-14 +extra: + kind: note +--- + +{{ fig(src="miru_time_to_kill_triple.jpg", alt="A graphic from the first pages of the Miru zine with the text ”Time to kill a god” overlaid on an imposing four-eyed figure.") }} + +## TL;DR + +It’s my favorite solo game, but it has some pacing problems. 8/10. + +## Miru, the solo analog adventure + +> 🎲 **Intended audience**: people who are familiar with basic tabletop role-playing game concepts. + +[**Miru**](https://hinokodo.itch.io/miru-an-analog-adventure-game) is a journaling solo RPG adventure by _Hinokodo_. + +Solo RPGs are pen-and-paper games that require only one player to play. Whereas most tabletop games require one player to take up the role of the game master or referee, solo RPGs replace this role with various mechanisms, such as an _oracle_ or a system of _random event tables_, paired with an increased player responsibility over the progression of the game. What would be ruled by the referee in a non-solo TTRPG, is resolved instead with a roll of the dice, or some other, potentially more mechanically curious procedure. + +Many solo games, Miru included, tell a single curated story. The focus can be very narrow: the game might take place in a specific location at a specific time, for instance. Miru does not allow you to create or customize your character either, which is par for the course. Solo systems are usually not generic enough to support arbitrary settings or adventure prompts. That is one of their strengths; the system can be tightly intertwined with the story in a way that would be inconvenient or even impossible in non-solo systems. + +Solo games are mechanically diverse and often involve innovative approaches to both visuals and game design. They also tend to be rather lightweight, including only the necessary rules or content in the often somewhat slim zine. Players often have to massage their brains a bit and deduce how some properties of the game interact. This is part of the appeal, for me at least. + +> I enjoy [The Soloist](https://soloist.substack.com/) and [Dan's News](https://danews.substack.com/) for my solo RPG news. + +_Journaling_ solo games are solo games that involve writing a journal of some kind as you progress. Miru asks you to write down a short rundown of what happened on each day of your adventure. You also draw the adventure map, hex by hex, on a blank map template. + +With definitions out of the way, let me start by saying that _Miru is the best solo adventure I have played_. The creative setting, the mechanics and distinctive visual identity come together to create a cohesive, well designed experience that I can wholeheartedly recommend. However, it is not perfect. There is a bit of a pacing issue during mid-game as well as some confusing or contradictory rules that are either explained by looking up the author's comments on the [itch.io](https://hinokodo.itch.io/miru-an-analog-adventure-game) page or not explained at all. These are minor issues, though, and can be circumvented with some _GM's fiat_ (which is of course also _player's fiat_ when you're flying solo). + +## First look + +{{ fig(src="miru_gameplay.jpeg", alt="The Miru zine, polyhedral dice, various stationery and other items strewn across a dining table.") }} + +Miru paints a picture of a dystopian, post-metropolitan society future where humans begrudgingly coexist with humanoid and animal-like robots. The story is kicked off in a scene where a farm worker robot kills a relative of the main character in an act of self defense when it evaluates their drunken hooliganism to be a threat. Stricken, the main character vows revenge and sets off to find out the truth about the machines’ origins. + +The game is a procedurally generated hex crawl with apocalyptic gear-scavenging mechanisms and straightforward combat with a dash of god-slaying thrown in for good measure. + +Hexes are generated by combining 3 oracle tables: + +> **_Hex type_** (mountain, grassland, swamp...) → **_Event type_** → **_Particular event_** + +Visiting a single hex takes one in-game day. The main gameplay loop is about generating a new hex, surviving the generated event, and planning your next move such that you don't lose too many resources, such as food or sleep. Near the endgame, this survival aspect turns into loadout optimization when you prepare for the final boss. + +There is also currency management. Money, or **bitliths** (a snarky reference to cryptocurrency), are used to purchase survival resources as well as equipment from villages. The available stock improves as you discover more villages: the first village has level 1 stock, the second one level 2, and all the following ones level 3. + +## My experience + +The beginning was excellent. I quickly [grokked](https://www.merriam-webster.com/dictionary/grok) the rules and the objective of the game. The first hexes were significant; you could never know if you're going to get important gear, or nearly die to a robot-wolf. The plot was nicely propelled forwards by special events that happened on predetermined dates, and character progression felt good. These events, as well as random events dropped little bits of lore here and there. As a big fan of From Software games and figuring out the world by reading item descriptions, this kind of soft worldbuilding hit the nail on the head. + +This well thought-out and balanced part of the game lasted maybe 10 to 20 in-game days. + +My entire playthrough lasted 41 days. In wall clock time, it lasted me over three of months. There wasn’t actually enough much gameplay to warrant multiple months of play; I just had an extended break near the near-endgame part. I felt that the pacing there was a bit off, every hex felt very same-y. I had obtained enough resources and gear for exploring to be very safe and so the random events began to mean less and less. Little by little, the grinding-adjacent preparation for the final boss fight started to feel repetitive, and so I inadvertently took a break from the game. + +I returned to the game just recently and decided that I'm just going to have a try at the boss. Screw getting the best gear, I will finish the game! This was a good call, since the battle was intense. I threw everything at my disposal at my opponent and was _just_ strong enough to defeat it with one (1) HP remaining! Talk about a close call. + +I will not spoil the final boss or the ending here, but I want to say a bit about it nevertheless. Depending on whether or not you picked up on the [solarpunky](https://en.wikipedia.org/wiki/Solarpunk) undertones of the game during your playthrough, the ending may come either as a bit of a surprise. It surprised me at least. + +There's also something that seems to be a hook for the next game in the series, [Miru 2](https://hinokodo.itch.io/miru-ii-an-analog-horror-game), subtitled "Analog Horror Game". The third installation, [Miru 3](https://www.kickstarter.com/projects/mimicpublishing/miru-3-an-analog-defense-game), is an "Analog Defense Game". I am left wondering what happens in the plot between parts two and three. + +I give Miru a solid 8 out of 10. The visuals, the theme, the mechanics, and the overall vibe are all excellent. The grindy bit in the middle was really the only thing that reduced my enjoyment somewhat. Note however that your mileage may vary. Everything is determined by a roll of the dice. + +## Mixed media addendum + +I love mixed media art. Combining disciplines and breaking traditional boundaries makes great art. + +With that in mind, the designer has released an official soundtrack called [Static Abyss](https://hinokodo.itch.io/static-abyss) for the Miru game series. The physical minidisk release of the soundtrack comes with some kind of game rules too, based on photos that I've seen. Sadly, it has sold out, possibly soon after its release in April 2023. The digital version is available on the itch.io page. + +The concept of a soundtrack for a physical game is badass. I own one physical release of such a thing, the Mörk Borg adventure [Putrescence Regnant](https://jnohr.itch.io/putrescence-regnant), which takes the form of a highly stylized LP disc and sleeve. Very cool. + +{{ fig(src="hinokodo-static-abyss.png", alt="A photo of the Static Abyss minidisk in its enclosure") }} +Photo attribution: [Hinokodo](https://hinokodo.itch.io/static-abyss) diff --git a/content/posts/subjective-review-miru/miru_gameplay.jpeg b/content/posts/subjective-review-miru/miru_gameplay.jpeg new file mode 100644 index 0000000..51d8d44 Binary files /dev/null and b/content/posts/subjective-review-miru/miru_gameplay.jpeg differ diff --git a/content/posts/subjective-review-miru/miru_time_to_kill_triple.jpg b/content/posts/subjective-review-miru/miru_time_to_kill_triple.jpg new file mode 100644 index 0000000..97be7fe Binary files /dev/null and b/content/posts/subjective-review-miru/miru_time_to_kill_triple.jpg differ diff --git a/content/posts/theres-an-rss-feed-now.md b/content/posts/theres-an-rss-feed-now.md deleted file mode 100644 index 8ec5448..0000000 --- a/content/posts/theres-an-rss-feed-now.md +++ /dev/null @@ -1,10 +0,0 @@ ---- -title: There's an RSS feed now -date: 2024-02-15 -extra: - kind: note ---- - -As part of the site build process, I use [`pandoc-rss`](https://github.com/chambln/pandoc-rss) to automatically build an RSS feed of my posts. - -![](https://jan.systems/files/internet_surf.png) diff --git a/content/posts/theres-an-rss-feed-now/index.md b/content/posts/theres-an-rss-feed-now/index.md new file mode 100644 index 0000000..e52db1a --- /dev/null +++ b/content/posts/theres-an-rss-feed-now/index.md @@ -0,0 +1,10 @@ +--- +title: There's an RSS feed now +date: 2024-02-15 +extra: + kind: note +--- + +As part of the site build process, I use [`pandoc-rss`](https://github.com/chambln/pandoc-rss) to automatically build an RSS feed of my posts. + +![](internet_surf.png) diff --git a/content/posts/theres-an-rss-feed-now/internet_surf.png b/content/posts/theres-an-rss-feed-now/internet_surf.png new file mode 100644 index 0000000..7787770 Binary files /dev/null and b/content/posts/theres-an-rss-feed-now/internet_surf.png differ diff --git a/content/posts/unfold.md b/content/posts/unfold.md deleted file mode 100644 index 241703a..0000000 --- a/content/posts/unfold.md +++ /dev/null @@ -1,42 +0,0 @@ ---- -title: Automatic slideshows with reveal-unfold.js -date: 2024-04-21 -extra: - kind: project - slideshow: defined ---- - -## Lightning talk - -

-I recently presented a lightning talk about the IndieWeb and personal websites to my colleagues at the office. Even though I don't normally enjoy presenting, speaking about things I'm passionate about always turns out to be rather easy. The talk and the presentation that I used to support the talk were well received. -

- -{{ fig(src="/files/indieweb_presentation.png", alt="A slide from the PowerPoint presentation") }} - ---- - -## Tinkering - -

-Later, when I shared my PowerPoint slides in the IndieWeb chat, I was asked if I had considered using HTML slides instead of pptx. The thought had crossed my mind, but I didn’t have time to put the thought into practice before my talk. It was a curious idea though. -

-

-So curious in fact, that I decided to study up on the ”HTML slideshow landscape”: what kind of tools there are, how do they work on a static site, things like that. Turns out that a tool called `reveal.js` is very popular and often recommended. An idea entered my mind: what if I could turn an HTML note (like this one that you’re reading) into an HTML slideshow, in a somewhat automated fashion? -

- -{{ fig(src="/files/reveal-js-logo.png", alt="Reveal.js logo") }} - ---- - -## Literate slideshow - -- A portmanteau of _literate program_ and _slideshow_ -- Convert a semantically marked up post to a slideshow with a click -- This post is a literate slideshow - ---- - -## Direction - -I don't know if I'll even need this project. Maybe I'll use it in some posts in the future? diff --git a/content/posts/unfold/index.md b/content/posts/unfold/index.md new file mode 100644 index 0000000..8ab3b89 --- /dev/null +++ b/content/posts/unfold/index.md @@ -0,0 +1,42 @@ +--- +title: Automatic slideshows with reveal-unfold.js +date: 2024-04-21 +extra: + kind: project + slideshow: defined +--- + +## Lightning talk + +

+I recently presented a lightning talk about the IndieWeb and personal websites to my colleagues at the office. Even though I don't normally enjoy presenting, speaking about things I'm passionate about always turns out to be rather easy. The talk and the presentation that I used to support the talk were well received. +

+ +{{ fig(src="indieweb_presentation.png", alt="A slide from the PowerPoint presentation") }} + +--- + +## Tinkering + +

+Later, when I shared my PowerPoint slides in the IndieWeb chat, I was asked if I had considered using HTML slides instead of pptx. The thought had crossed my mind, but I didn’t have time to put the thought into practice before my talk. It was a curious idea though. +

+

+So curious in fact, that I decided to study up on the ”HTML slideshow landscape”: what kind of tools there are, how do they work on a static site, things like that. Turns out that a tool called `reveal.js` is very popular and often recommended. An idea entered my mind: what if I could turn an HTML note (like this one that you’re reading) into an HTML slideshow, in a somewhat automated fashion? +

+ +{{ fig(src="reveal-js-logo.png", alt="Reveal.js logo") }} + +--- + +## Literate slideshow + +- A portmanteau of _literate program_ and _slideshow_ +- Convert a semantically marked up post to a slideshow with a click +- This post is a literate slideshow + +--- + +## Direction + +I don't know if I'll even need this project. Maybe I'll use it in some posts in the future? diff --git a/content/posts/unfold/indieweb_presentation.png b/content/posts/unfold/indieweb_presentation.png new file mode 100644 index 0000000..e9d0096 Binary files /dev/null and b/content/posts/unfold/indieweb_presentation.png differ diff --git a/content/posts/unfold/reveal-js-logo.png b/content/posts/unfold/reveal-js-logo.png new file mode 100644 index 0000000..f7b671f Binary files /dev/null and b/content/posts/unfold/reveal-js-logo.png differ diff --git a/content/projects/ATK16.md b/content/projects/ATK16.md deleted file mode 100644 index 204220d..0000000 --- a/content/projects/ATK16.md +++ /dev/null @@ -1,627 +0,0 @@ ---- -title: ATK16 -weight: 0 -extra: - kind: project ---- - -> This is a live document describing the current state of the **ATK16** project. It is not final and will most likely never be. Follow the [note archive](../archive) RSS feed to be notified when significant changes to the project are introduced. - -- [Abstract](#abstract) -- [Namesake](#namesake) -- [Processor architecture](#processor-architecture) - - [CPU and the instruction cycle](#cpu-and-the-instruction-cycle) - - [Registers](#registers) - - [Arithmetic-logic unit (ALU)](#arithmetic-logic-unit-alu) -- [Memory](#memory) - - [ROM](#rom) - - [RAM](#ram) - - [MMIO segment](#mmio-segment) -- [Interrupts](#interrupts) -- [Picture and sound](#picture-and-sound) - - [Text processing unit (TPU)](#text-processing-unit-tpu) - - [Picture processing unit (PPU)](#picture-processing-unit-ppu) - - [Audio processing unit (APU)](#audio-processing-unit-apu) -- [Application binary interface (ABI)](#application-binary-interface-abi) - - [Instruction set](#instruction-set) - - [Calling convention](#calling-convention) - - [Data type representation](#data-type-representation) -- [Toolchain](#toolchain) - - [Automated tests](#automated-tests) -- [Assembly language](#assembly-language) - - [Statements](#statements) - - [`@include` and `@use`](#include-and-use) - - [`@data` and `@let`](#data-and-let) - - [`@label` and `@address`](#label-and-address) - - [Syntax highlighting](#syntax-highlighting) -- [Standard library and built-ins](#standard-library-and-built-ins) - - [`bootstrap`](#bootstrap) -- [Links](#links) - -## Abstract - -**ATK16** is a homegrown faux-retro 16-bit computer and ecosystem project that includes - -- a non-pipelined, Von Neumann style, register-machine, central processing unit (CPU) design -- a small, custom instruction set architecture (ISA) with 4-bit opcodes, i.e. 16 possible instructions -- on-board peripherals (memory, graphics and sound processors) -- memory-mapped input and output (MMIO) for communicating with on-board peripherals as well as external peripherals such as the keyboard -- 16-bit data and address buses -- a big-endian byte order -- an assembly language -- an assembler for building software for the system -- an application binary interface (ABI), including a function calling convention and data memory representations, designed with the system's features in mind -- a software emulator for running the built software on your development machine -- a register transfer logic (RTL) model designed in [Digital](https://github.com/hneemann/Digital) capable of running the built software - -{{ fig(src="/files/atk16-block-diagram.svg", alt="ATK16 block diagram") }} - -The idea for the project hatched from my admiration for [Ben Eater](https://eater.net/) and his DIY CPU and video card projects. I was enamoured by the elegance of his systems and simply wanted to do something similar. I had none of the required knowledge for this kind of low-level CPU design work, but I had studied digital electronics for a bit in university. - -The technical details are an amalgam of the features of home computers and game consoles of the 80s and 90s, without all the optimization and smart engineering. The idea of **ATK16** is _not_ to create a performant, useful system. The idea is to learn and make something that runs programs and makes bleeps and bloops. - -The project started off as just the CPU design, but grew little by little to encompass also peripheral circuits as well as the development toolchain and software libraries. The scope defined in this document is approaching the final scope asymptotically. Nothing major has been added for some time and I'd like to keep it that way. - -As of right now, **ATK16** is capable of running text mode graphical programs with keyboard input in both the emulator and the simulator model. Sound and sprite graphics are still underway. My dream is that some day I have a big program, be it a dungeon-crawler game or some productivity software, that uses all the features available in the system in unison, and I can use that for demoing the thing. - -The spec undulates in time based on what I deem possible or necessary for the system's design. And by spec I mean a categorical product of what I have implemented thus far and what I'm planning to implement in the future. There is no written spec. In fact, this document is possibly the closest thing there is to a written spec. - -## Namesake - -ATK, or _automaattinen tietojenkäsittely_, is a Finnish term that translates to "automated information processing". The acronym is no longer used; it has been supplanted by the English acronym _IT_ even in Finnish contexts. Being now a historical expression, the term has gained a certain affect. It's like talking about automobiles instead of cars, or telephoning someone instead of calling them. There is also a bit of awe embedded in the expression: it takes effort to understand _ATK_. - -Talking about _ATK_ in current day invokes that little bit of mystique about not fully understanding how computer technology works or is supposed to work. It can also be used ironically: IT systems that malfunction, are non-optimal or just generally bad can be described as _ATK_ in Finnish techie vernacular. - -For me personally, _ATK_ brings to mind a yellowed plastic box that's running something like UNIX System V and some kind of corporate bookkeeping software. Entirely dull but somehow intriguing at the same time. - -**ATK16** is ATK with a 16-bit bus width. - -## Processor architecture - -The processor consists of the central processing unit (CPU), the arithmetic-logic unit (ALU), the register bank and the memory unit, all connected by the data bus to each other as well as onboard peripherals such as the sound and graphics cards, as well as external peripherals like the keyboard. The processor's purpose is to run user-written programs by executing instructions that are stored in memory. - -### CPU and the instruction cycle - -The heart of the system is the CPU. In classical Von Neumann style, its heartbeat consists of three stages: - -- **Fetch**: pull the next instruction from memory to the instruction register (IR) -- **Decode**: split the instruction into the operation code (opcode) part and argument parts -- **Execute**: convert the opcode into a series of control signals that operate on the arguments of the instruction, moving them between memory, registers, the arithmetic-logic unit (ALU) and the peripherals using the data bus, effectively executing the operation - -The CPU is not pipelined, which means that only one instruction is being processed at once. This makes the instruction cycle and the CPU state really easy to reason about. It also induces a big performance hit: potential amortized performance is reduced to around a third. - -Pipelining would allow work to be done in parallel, but also opens the door to jump hazards and other possible issues that have to be taken care of. - -There is also only one core; no multicore parallelism for you. - -### Registers - -The register bank consists of 8 _general use registers_ `RA`–`RH`. These are the registers that are usually being referred to when using the word _register_. - -There are also _special registers_: - -- a program counter (PC) for keeping track of the program execution position -- a memory address register (MAR) for storing the memory address being accessed -- an instruction register (IR) for storing the instruction that is being executed -- a flag register (FR) for storing the ALU flags that are updated on each ALU operation -- an interrupt program counter register (IPC) for storing the program counter value when entering an interrupt service routine (ISR) -- ...as well as other, unnamed registers that are irrelevant for understanding the operation of the system. - -Special registers can not be accessed with the same instructions that access the general use registers. - -Most instructions in the ISA have to do with moving data between these registers, the ALU and the memory. Take for example this assembly program: - -```clojure -ldi 0x100 RA ; load the value 0x100 to register RA -ldr RA RB ; read from memory address in RA (0x100) and store the value in RB -addi RB 1 RC ; add the immediate value 1 to the value in RB and store the result in RC -``` - -> 🙋🏼 Note: throughout this document, I will be using assembly language examples anachronistically to demonstrate system features even though you, the reader, might not yet know how to read it. You can check out later parts of the document for a detailed description of the assembly language if you prefer learning that way, although I'll try to write my examples in such a way that the language is comprehensible in context even without knowing the syntax or the semantics. - -Using simple operations, data is being moved from the instruction arguments to a register, from the register to the MAR, from the MAR to the memory, from the memory to another register, from the register to the ALU, from the ALU to the FR and another register. - -In addition, there exists the notion of _memory registers_, which are special memory addresses that simulate a hardware register. They exist because they can be accessed via regular memory reads and writes; no purpose-built opcodes need to be added. Most of the memory registers are MMIO registers. Accessing MMIO registers allows the user to access peripherals and do actions such as put pixels to the graphics cards or read key code values from the keyboard. - -> Note: `RH` is conventionally reserved to be used as a stack pointer (SP), so that leaves 7 registers for general use. Note that nothing in the hardware requires a stack pointer or mandates its use. However, a stack is a nice thing to have, and reserving a hardware register for the stack pointer (instead of a memory address) makes stack operations a lot faster. The `bootstrap` assembly module assumes that `RH` is the stack pointer. - -### Arithmetic-logic unit (ALU) - -ATK16 has an 8-operation ALU that takes in two values and produces a third and sets a bunch of flags. The two input values can come either from - -- register X and register Y, or -- register X and an immediate value IMM between 0 and 7. - -The output goes to register Z. - -> X, Y and Z are general use registers `RA`–`RH`, selected by the instruction arguments. - -For example, this 16-bit bitstring represents an instruction that sums `RB` and `RC` and saves the result in `RD`: - -```clojure -CCCC TTT LLL RRR SSS -0000 011 001 010 000 -``` - -Here, - -- `CCCC` is the 4-bit opcode `ALR` (arithmetic-logic with register), -- `TTT` is the 3-bit target register `RD`, -- `LLL` is the 3-bit left hand side register `RB`, -- `RRR` is the 3-bit right hand side register `RC`, and -- `SSS` is the 3-bit ALU code for the operation `L + R`. - -The full list of ALU operations is below. - -| ALU code | Operation | -| :------- | :-------- | -| 0 | L + R | -| 1 | L - R | -| 2 | L and R | -| 3 | L or R | -| 4 | L xor R | -| 5 | L >> R | -| 6 | L >>> R | -| 7 | L << R | - -> The difference between `L >> R` and `L >>> R` is that the first one is a _logical right shift_, meaning that the new bits at the most significant end of the word are always set to zero. The second one is an _arithmetic right shift_, meaning that the new bits all match the most significant bit of the original word. This is useful in signed arithmetic, where a right shift should result in a negative result (most significant bit = 1) when applied on a negative number. - -Keep in mind that the operands are always 16-bit. Any result that cannot be represented with 16 bits will over or underflow to fit. In such cases, a flag is set to signal that the arithmetic result is not correct in a real world sense. - -After each ALU operation, the flag register (FR) is updated with the new flags. The flags are as follows: - -| Flag | Interpretation | -| :----------- | :------------------------------------------------ | -| Carry (C) | In unsigned arithmetic, the result is not correct | -| Overflow (O) | In signed arithmetic, the result is not correct | -| Zero (Z) | The result is zero | -| Sign (S) | In signed arithmetic, the result is negative | - -The ALU assumes two's complement negative numbers, which makes it possible to use the same operations for both unsigned and signed arithmetic. As a user you have to choose which one you're using, and interpret the ALU flags and the result value correspondingly. - -## Memory - -The CPU is connected to the onboard memory unit. The memory unit consists of multiple components that are accessed through a single address bus. The address bus width, i.e. the size of the address, is 16 bits. This means that the largest address that can be represented is `2^16 - 1 = 0xFFFF`. In other words, there are 64K addresses. - -The memory consists of a couple of major segments: the ROM, the RAM and the MMIO segment. - -| Segment | From | To | -| :------------------------- | :----- | :----- | -| Read-only memory (ROM) | 0x0000 | 0x7FFF | -| Random access memory (RAM) | 0x8000 | 0xE7EF | -| -- Stack | 0x8000 | X | -| -- Heap | X | 0xE7EF | -| MMIO segment | 0xE7F0 | 0xFFFF | -| -- MMIO registers | 0xE7F0 | 0xE7FF | -| -- Sprite memory (PPU) | 0xE800 | 0xFFFF | -| -- Text memory (TPU) | 0xF800 | 0xFFFF | - -Let's look at each of these in detail. - -> The X between the stack and the heap represents some dynamic point that they both grow toward. If the stack and heap overlap, weird things and data loss may occur. - -### ROM - -The ROM is a memory that contains constant data and program instructions. The ROM can not be written to during runtime, only during development using tools external to the **ATK16** system. - -The ROM is initialized with a ROM image that is produced by the ATK16 assembler. The assembler takes in your assembly and produces a binary image exactly 64 KB in size. - -> ✍🏻 Vocabulary check: an _image_ is just a binary file full of bytes that make sense to a specific system or program. Usually images contain executable instructions, but there are also _disk images_ that contain the contents of a hard drive, byte for byte. Here we are talking about the first type of image. - -With a physical ROM (e.g. EEPROM), the ROM image would written to the ROM using a separate programmer circuit. We have an easier time with the ATK16 emulator, which simply takes in the filesystem path to the image as a command line argument and reads it directly from the file. The Digital simulator model similarly supports loading the image into the ROM component. - -On boot, the program counter (PC) is set to zero. When the machine starts up, the first instruction cycle reads from the address 0, which is the first word of the ROM image. This segment of the image is something akin to the boot sector of modern computers: its task is to set up critical things, such as the stack pointer, and then jump to the actual program segment. - -Between the boot sector and the program segment, which contains the user-supplied program code, there is the _vector table_ and the _data segment_. The vector table is a sequence of important, spec-defined values located at predefined addresses. - -> For example, at address `0x14` you should find the value `0x8000`. This is the address of the lowest stack address, and the beginning of the stack segment. - -The vector table needs to exist in a low address segment, since the operation `ldi` (load immediate), which is the only way to load values from instruction arguments to registers, can only load `2^9 = 512` distinct immediate values, starting from zero. Loading larger values with `ldi` requires you to first store the value in a convenient low address (i.e. `0x1FF` or less), and then load that with `ldr` (load from address in register). - -Large spec-defined values exist in the vector table and user defined values exist in the _data segment_. The data segment is dynamically sized and grows with each word of data stored in it. You can use the `@data` assembler directive to install a new word of data into the data segment. - -### RAM - -The RAM is a read-and-write memory that contains both the stack and the heap. The stack is a "hardware level" thing: there are instruction mnemonics (`spu` and `spo`) for manipulating the stack. The stack grows upwards toward larger addresses. - -> 🙋🏼 Side note: the mnemonics `spu` and `spo` are actually macros and not primitive instructions on the ATK16. Macros are a way of constructing compound mnemonics that are expanded to their primitive constituent parts during assembly. - -The RAM is full of scrambled, uninitialized gibberish at startup. The first instructions in the boot sector are responsible for setting up the stack pointer in register `RH` and pointing it to the beginning of the RAM and the stack segment, i.e. `0x8000`. - -The heap is a "software level" thing. There is nothing in the assembler that indicates the existence of a heap, or a heap allocator for that matter. The heap is purely defined in software. You can have a running ATK16 without a heap and instead just have static allocations and a big chunky stack. There is a standard library module for setting up a heap allocator though; you don't have to invent one yourself. The heap grows downwards toward smaller addresses. - -> Note that you can technically forgo the stack as well, but that's kind of wild. - -### MMIO segment - -The _memory-mapped input and output_ (MMIO) segment is a part of the address space that is concerned with accessing and manipulating peripherals and special registers. Some of the addresses in this segment are read-only, some are write-only, and some may be read-write. Running a non-supported read or write operation results in gibberish data or unexpected behaviour. - -In the beginning of the MMIO segment there is a small sliver of address space that houses the MMIO registers. - -| Address | Name | Description | -| :------ | :-------------- | :------------------------------------------------------------------------------- | -| 0xE7F0 | `terminal_addr` | Write-only, write characters to the terminal peripheral | -| 0xE7F1 | `keyboard_addr` | Read-only, read key codes from the keyboard peripheral | -| 0xE7F2 | `gr_mode_addr` | Write-only, set the graphics mode (0 = disabled, 1 = text mode, 2 = sprite mode) | -| 0xE7F3 | `iset_addr` | Write-only, set the critical section flag (see [Interrupts](#interrupts)) | - -The MMIO segment continues with the _sprite and text memories_. These are separate memories and not a part of the main RAM. Note that the sprite and text memory address spaces overlap: this is fine, because the two are never used at the same time. Sprite memory is an auxiliary memory in the picture processing unit (PPU) and text memory is an auxiliary memory in the text processing unit (TPU). The graphics mode determines which is producing a video signal to the VGA peripheral. See the [Picture and sound](#picture-and-sound) section for more detail. - -## Interrupts - -The ATK16 is a single-tasking machine, but there is still one mode of concurrency: interrupts. Interrupts are a mechanism for pausing the current execution and running a bit of special program code called an _interrupt service routine_ (ISR) instead, when a time-sensitive event occurs. When the ISR finishes, the CPU restores the paused program and continues where it left off, as if nothing happened in the meantime. - -To store and restore the program state, the interrupt handler uses special instructions to move the program counter (PC) value into and out of the interrupt program counter (IPC) register. - ---- - -So what causes interrupts? The most common cause would be a keyboard key press. On key press, the keyboard module sets an _interrupt line_ from 0 to 1. This is referred to as an _interrupt request_ (IRQ). There are four interrupt lines available for different peripherals to use. These are not configurable: a keyboard always uses `IRQ0`. The rest are reserved for future designs. - -At the beginning of each instruction cycle, a component of the CPU called the _interrupt handler_ checks if any of the IRQs are set, and prioritizes the one with the lowest index. If there is no IRQ active, the CPU continues normally. If there is an active IRQ, the handler overrides the IR and runs a special instruction to save the PC to IPC as well as read the new PC value from the vector table. Each interrupt line has an entry in the vector table that tells the system where in memory to find the corresponding ISR code. The PC is updated with this address and execution continues from the beginning of the ISR. - -The ISR code has some obligations. It is the ISR’s responsibility to leave the register bank seemingly untouched when control is returned back to the main program. To do this, all register values that the ISR erases by using the register for its own logic must be saved on the stack, and restored before returning. This is similar to the function calling convention, where functions are obligated to clean up after themselves. - ---- - -Interrupts are a source of concurrency since IRQs might be serviced at any point in the program, possibly leading to data inconsistency issues. - -Consider this scenario: a main program does three things in a loop: - -1. it reads a value (`n`) from memory into a register, -2. it increments it by one (`n + 1`), and -3. it stores it back in the same memory address. - -An interrupt pauses the execution right after the memory load (between steps 1 and 2). The ISR decrements the same value in memory (`n - 1`) and returns to the main program. The main program is now in an unexpected state: the loaded value in the register is one more than the one in memory (`n > n - 1`). Continuing forward, the program increments the register value by one (`n + 1`) and stores the result in memory. The value in memory effectively jumps from `n - 1` to `n + 1`. So much for incrementing by one. It's as if the ISR never decremented the value in the first place. - -This is called a _race condition_: the effect of a program depends on the timing of concurrent tasks. To avoid race conditions, a section of code can be declared a _critical section_. A critical section can not be interrupted. If an interrupt request happens to come in during the execution of a critical section, it stays active but control won't be transferred to the ISR. When the critical section ends, pending interrupt requests are immediately processed. - -All parts of your program that access the same data as your ISRs should always be declared critical sections! - -> Note: upon entering an ISR, the system itself toggles the critical section flag. This is done to make sure that only one interrupt can be running at once. Concurrent interrupt requests are serviced sequentially in index priority order. - -Interrupts caused by events in physical peripherals are called _hardware interrupts_. Some computers also support software interrupts, i.e. interrupts triggered from program code. ATK16 does not have these per se, but it does support setting the critical section flag from software via an MMIO register. This is used to declare a critical section in program code. - -## Picture and sound - -Video and audio are the primary means of providing feedback to the user about what is happening in the machine. The video part is handled by the two onboard graphics processors: the TPU and the PPU. - -On startup, the _graphics mode_ memory register is set to disabled (0). To activate the TPU, set the value to _text mode_ (1), and to activate the PPU, set the value to _sprite mode_ (2). - -### Text processing unit (TPU) - -The TPU is a graphics processor that produces a 40 x 30 character VGA output signal. - -{{ fig(src="/files/atk16-text-mode.png", alt="A screenshot of a text mode program displaying a work-in-progress hardware monitor") }} -The TPU has 3 major components: - -- The character memory (ROM) -- The text memory (RAM) -- The VGA signal generator - -The character memory is a bit of ROM memory that is loaded with a _character memory image_. This is a small binary image that contains pixel data that allow the VGA signal generator to draw a character based on its character code. The image can be generated with the `charmem.py` tool. The character set used in the TPU contains 256 8x8 glyphs. - -The text memory is a block of RAM that contains character codes. One character of text can be represented in one 8-bit byte of space. For ease of implementation, each 40 character row is represented by `2^6 = 64` words. - -> This is obviously wasteful, since we're losing 24 words to undisplayable data every row. We could even pack two characters into one word, in total reducing the size of the text memory to less than 50%! -> -> These are improvements that might be implemented later, but for now, I'm focusing on getting the thing working. - -You can write into the text memory using regular memory instructions with the MMIO addresses from `0xF800` upwards. - -The VGA signal generator is a circuit that tirelessly iterates over the character and text memories, synthesizing their contents to generate pixel data for each pixel of the 320x240 VGA display. - -### Picture processing unit (PPU) - -The name and the design for the PPU are shamelessly stolen from the Nintendo Entertainment System (NES). - - - -**TBD**. - -### Audio processing unit (APU) - -**TBD**. - -## Application binary interface (ABI) - -The ABI is a platform specified set of details that describe the interface between two executable binary program modules. These could be for example a compiled application and a compiled library in a modern computer. The details of the ABI include things like how to call functions and how to represent data in memory. - -In **ATK16**, there are never two separate binary objects that need to interface each other, since all source modules supplied to the assembler are compiled into a single output image. There are no build units, no linking stage, and no libraries to load. Everything is assembled into a single image. Without two things interfacing, the term _application binary interface_ is admittedly a bit weird, but I still use it because it is commonly used to talk about the details that I want to talk about in this section. - -> Build units and a linking stage are mostly a historical byproduct of development systems that could not hold the entire build context in memory at the same time. Building nontrivial projects was made possible by allowing the compiler to work on only a little bit at a time. This is useful even today when building very large projects. -> -> When compiling for the **ATK16**, this issue is nonexistent. Any modern system has enough RAM to build a max size, 64KB ROM image. - -### Instruction set - -The instruction set is _minimum instruction set computer_ (MISC) inspired. It is not truly minimal; multiple instructions, for example `jpr` ja `brr` could probably be replaced by elaborate multi-jump schemes. However, it is still quite small, and the opcodes fit nicely in 4 bits (16 available values). - -| Opcode | Mnemonic | Description | Arguments | -| :----- | :------- | :------------------------------ | :------------------------------ | -| 0000 | alr | Arithmetic-logic with register | left reg, right reg, target reg | -| 0001 | ali | Arithmetic-logic with immediate | left reg, imm value, target reg | -| 0010 | ldr | Load from address in register | address reg, target reg | -| 0011 | str | Store to address in register | value reg, address reg | -| 0100 | ldi | Load immediate | immediate value | -| 0101 | jpr | Jump to address in register | address reg | -| 0110 | jpi | Jump to immediate address | imm address | -| 0111 | brr | Branch to address in register | imm flag selector, address reg | -| 1000 | bri | Branch to immediate address | imm flag selector, imm address | -| 1001 | lpc | Load program counter | target reg | -| 1010 | – | – | – | -| 1011 | – | – | – | -| 1100 | isrp0 ¹ | ISR process 0 | – | -| 1101 | isrp1 ¹ | ISR process 1 | – | -| 1110 | rti | Return from interrupt | – | -| 1111 | hlt | Halt | – | - -> ¹ `isrp0` and `isrp1` are not intended to be used in program code. They are "magic" instructions that override the current instruction when servicing an interrupt request. See [Interrupts](#interrupts). - -All instructions have a fixed 16-bit width. 4 bits are reserved for the opcode and 12 bits are reserved for the argument data. - -Register arguments use a 3-bit selector value to pick one of the 8 general use registers `RA`–`RH`. Immediate arguments are either 3 bits wide (`ali`) or 9 bits wide (`ldi`, `jpi`, `bri`). - -Citing the [ROM](#rom) section: - -> The vector table needs to exist in a low address segment, since the operation `ldi` (load immediate), which is the only way to load values from instruction arguments to registers, can only load immediate values up to `2^9 - 1 = 511`. Loading larger values with `ldi` requires you to first store the value in a convenient low address, and then load that with `ldr` (load from address in register). - -This limitation gives rise to a common pattern where larger values are stored in low addresses in the data segment, and then loaded to registers with a combination of `ldi` and `ldr`. - -Unconditional jumps (`jpi`, `jpr`) do what it says own the label: they jump to the given address without checking any condition. Conditional jumps, or branches (`bri`, `brr`) use a 2-bit flag selector to check if a specific flag register (FR) flag is set (see ALU), and only then do the jump. If the specified flag is not set, the branch instruction is a no-op. - -In general, ATK16 uses absolute addressing everywhere, except in the immediate jumps (`jpi`, `bri`), which use relative addressing (`result = PC + address`). This is useful because most of the immediate jumps are short distance and can be represented in 9 bits if using relative addressing, but not absolute addressing. Long jumps must use a `ldi` + `ldr` + `jpr` pattern, which is based on the `ldi` + `ldr` pattern that is used to read large values to registers. - -For more detail on how arguments are packed into the instruction word, see source code of the `ucode.py` command line tool. - -### Calling convention - -To call a function, the caller must set the first argument to `RA`, the second to `RB`, etc. Naturally, a maximum of seven (7) arguments can be passed, since `RH` is the stack pointer by spec. The caller then pushes the return address `PC + 1` to the stack and jumps to the function address. - -> If you need to pass more than 7 arguments, consider passing a pointer to a struct instead. - -The callee must make sure that the registers contain the same values at return time as they did at call time, with the exception of the return value register, which is defined as `RG` by spec. To do this, the called function must save and restore any registers it uses in its body. - -> The idea here is that register values are locally consistent. All register manipulations should be explicit in program code. If a function call would be allowed to clobber registers (other than `RG` which is allowed to be clobbered), it would be difficult to reason about the local behaviour of a program. - -The mechanical parts of this calling convention are implemented by the `calli`/`callr` and `return` built-in macros. The calling user only needs to set up arguments in the correct registers and read the return value from the return value register. - ---- - -Note that function calls are similar to interrupts in multiple ways: - -| Function call | Interrupt | -| :-------------------------------------------------------------- | :-------------------------------------------------- | -| The return address `PC + 1` is saved on the stack | The PC is loaded to the IPC register | -| The PC is replaced by the function address | The PC is replaced by the ISR address | -| The called function is responsible for restoring register state | The ISR is responsible for restoring register state | -| The function returns by jumping to the return address | The ISR returns with the instruction `rti` | - -### Data type representation - -> This is an area of active development. - -ATK16 has a canonical memory representation for primitive data types like integers and booleans, as well as some compound data types like strings and structs. The standard library expects data to be stored using these representations. Users are free to define custom memory representations for new types and implement procedures to access and manipulate them in their applications. - -All data types defined here align with the 16-bit word boundary. - -| Type | Bits | Representation | -| :--------------------------------- | :---------------- | :-------------------------------------------------- | -| Unsigned integer | `16` | Binary number | -| Signed integer | `16` | Two's complement, binary number | -| Character | `16` | 8 bits padding, 8 bits of character code | -| Boolean | `16` | Zero for `false`, any other bitstring for `true` | -| Fixed-point fraction with base _b_ | `16` | Binary integer (base only known in compile time) | -| Array of _n_ elements of type _a_ | `n x sizeof(a)` | Consecutive bytes / words in memory | -| String of _n_ characters | `16 + n x 16` | Pascal string, i.e. the length followed by the data | -| Struct with fields _a0_, _a1_, ... | `sum(sizeof(aN))` | Consecutive field representations in memory | - -> 🙋🏼 Future note: A new packed string representation might happen in the future. In a packed string, two 8-bit characters are stored in one 16-bit word. This would mean that the 16-bit word alignment rule is relaxed to 8-bit byte alignment string-internally. The string representation as a whole is still padded to the word boundary. - -## Toolchain - -The **ATK16** project is a combination of designs and implementations. In addition to the Digital simulator model and the emulator, which are implementations of the system itself, there are also a number of command line tools. The table below summarizes the major directories available in the ATK16 monorepo. - -| Name | Description | -| :---------------------------- | :---------------------------------------------------- | -| atk16_emu | Software emulator | -| atk16_asm | Assembler | -| digital_diagrams | Digital logic simulator model | -| atk16_syntax | VSCode syntax highlighting for ATK16 assembly | -| atk16_utils | CLI tools to generate the character memory image etc. | -| atk16_projects | Example application projects | -| test | Automated tests (`make test`) | -| \* atk16_bytecode_compiler | Python -> ATK16 assembly compiler | -| \* atk16_ast_walking_compiler | Python -> ATK16 assembly compiler | - -> \* ) These are more of an academic experiment in compilers and not a serious component of the ATK16 toolchain. The compilers are not implemented to a working degree and only support a very limited subset of Python 3. It turns out that while trying to compile a language that is totally not meant to be used as a systems language is a fun thing attempt, alas, it does not lead to a fruitful outcome. - -All programmed tools are written in reduced dependency Python 3. The only external dependencies are `getch` and `pygame`, which are used for unbuffered keyboard input, and audio and video, respectively. - -The easiest way to get something running is to use the emulator. Follow the steps below to get the `monitor` application booted up. - -1. `git clone` the ATK16 repository -2. Set up a suitably modern Python 3 environment and install the ATK16 toolchain as a local Python package: - -```sh -$ pip install . -``` - -3. Assemble the `monitor.atk16` source into a ROM image: - -```sh -$ atk16c resources/asm/monitor.atk16 -o out/monitor.bin -``` - -> The assembler also produces another file in the output directory called `out/monitor.bin.dbg` that contains debug symbols. The step debugger uses these to map instruction words to their original source lines. - -4. Load the built image into the emulator and boot it up: - -```sh -$ atk16emu out/monitor.bin -``` - -You can supply the `-d` flag to the emulator to start the program in step debugger mode. Press `?` to see usage instructions. - -```sh -$ atk16emu -d out/monitor.bin -``` - -### Automated tests - -If you plan to make changes to **ATK16** components you can run the test suite by running the following in the monorepo root: - -```sh -$ make test -``` - -These tests utilize some useful utilities that can be useful when writing tests for your own application code as well. See the test source code in the `test` directory for details. - -## Assembly language - -Assembly languages are a family of low-level programming languages whose statements strongly correspond with the machine code instructions of their target platform. _ATK16 assembly_ is an assembly language designed to be understood by the ATK16 assembler and compiled into a ROM image for ATK16 to read and execute. - -The language aims to be as human-readable as an assembly language realistically can. To aid in this, the assembler supports _macros_, which allow the programmer to construct named reusable blocks of code that are expanded to primitive instructions at assembly time. Using macros has no effect on performance when compared to writing out instructions by hand. - -Here's a code snippet: - -```lisp -@use monitor_macros:* - -@include %bootstrap -@include %std_mem -@include %std_term -@include %std_bump_alloc - -;; Constants -@data text_buffer_size 2048 -@data input_buffer_size 64 - -;; Global variable pointers -;; 0xE800 - 0xEFFF: sprite buffer space that is unused in this program -@data text_cursor_p 0xE800 -@data input_buffer_p 0xE801 -@data input_cursor_p 0xE802 - -@data title_string " »» ATK16 monitor v0.1 »»" -@data subtitle_string " Run [help] for a list of commands" -@data prompt_string " > " - -@label main -;; initialize heap allocator - calli bump_reset - -;; initialize global variables -;; text_cursor_p = text_mem - ldi text_cursor_p RA - ldr RA RA - ldi vt_text_mem RB - ldr RB RB - str RB RA -``` - -Let's dissect this. - -> 🙋🏼 The pandoc-based code highlighting that you're seeing on this page naturally does not support ATK16 assembly. I try to use highlighting for other programming languages to get some sort of readability. - -### Statements - -Statements are sequences of code delimited by a newline. Other types of whitespace (i.e. not newlines) separate terms in a statement. Statements that start with `@` are called directives. Comments start with a semicolon `;` and end at a newline character or EOF. - -Indented statements are _instructions_ or _constant values_. The indentation is only a convention that makes it easier to visually separate directives from the rest of the program. Non-indented instruction or constant value statements are permitted but are considered unconventional. - -An instruction starts with a mnemonic (e.g. `ldi`) that is either a primitive instruction (see [Instruction set](#instruction-set)) or a macro. Macros are either user-defined or built in to the assembler (such as `calli`). Constant value statements are just numeric values. - -These are all valid statements: - -```lisp - 0xFF - 0b1 - 10 - ${ord("A")} ; evaluate Python expr to value 65 - jpi main ; jump to label "main" - bri zero branch ; jump to label "branch" if zero flag is set -``` - -A numeric constant must be representable in 16 bits. - -Symbols `RA`–`RH`, `carry`, `overflow`, `zero` and `sign` are defined for convenience and evaluate to numeric values `0`–`7`, `0`, `1`, `2` and `3` respectively. - -You can use `${ ... }` to evaluate an arbitrary Python expression as part of a statement. - -### `@include` and `@use` - -At the top we see some _assembler directives_: `@include` and `@use`. These directives are similar in spirit: they include other code into the assembly context. `@include file` injects the contents of the file `file.atk16` at the location of the directive. File names prepended with a `%` sign are [Builtin modules](standard-library-and-built-ins). For you C and C++ programmers, there is an implicit `#pragma once` in every module: including a file is idempotent. Even if you `@include` a file multiple times, only a single copy of the contents will be assembled into the output image. - -You can use `@use file:*` to import macros from a Python module called `file.py`. Macro modules are Python files that define a dictionary called `extensions` that maps symbol names to functions that returns a list of lists of statement terms. Here's a snippet from the `monitor_macros.py` file that the above assembly code uses: - -```python -from atk16_asm.asm_ops import * - -expansions: dict[str, Callable[..., ExpandResult]] = {} -def register_macro(func): - expansions[func.__name__] = func - return func - -@register_macro -def m_newline(r1: str, r2: str) -> ExpandResult: -return [ - *expand_ldi("text_cursor_p", r1), - *expand_ldr(r1, r1), - *expand_ldr(r1, r2), - *expand_slri(r2, "6", r2), # cursor = cursor / 64 - *expand_addi(r2, "1", r2), # cursor = cursor + 1 - *expand_slli(r2, "6", r2), # cursor = cursor * 64 - *expand_str(r2, r1), -] -``` - -There is only one namespace and no scoping in ATK16 assembly. If you `@include` or `@use` a resource in one file, its contents may become visible to other files as well, depending on the inclusion tree traversal order. Library developers are instructed to prefix all symbols with the library name so that the probability of a namespace clash is reduced. Although, since all assembly is done from source code only, you can fix namespace conflicts with a simple search-and-replace. - -> However, Python macros `@use`’d in an `@include`’d assembly module are never visible in the parent includer context. This is because `@include` creates a separate assembler context that does not leak its bindings to the parent context. - -### `@data` and `@let` - -Next, you can see some `@data` directives. These are used to inject constant value bindings to the _data segment_, which is a location in memory whose starting address is determined with the `@data_segment` directive. The `bootstrap` module, which is the recommended system entrypoint for most programs, defines a data segment that can be used in user programs. - -A constant binding `@data X ` injected into the data segment is available in the rest of the program as if it was defined with a label directive: `@label X`. The data is represented in memory according to the [Data type representation](#data-type-representation) spec. Currently, only integer and string values are supported. - -`@let X Y` can be used to define symbol bindings that only exist during assembly time. This is useful for giving name to otherwise magic numbers. Referencing the symbol `X` simply does an environment lookup and evaluates to `Y`. - -### `@label` and `@address` - -The `@label main` statement defines a label called `main` that points to the address in ROM that contains the instruction or datum on the following line. Labels are a way to refer to a specific memory location without knowing its absolute address. Labels can be used in most places where a numeric value is expected. The most common use case is as the target of a jump instruction: - -```clojure -@label loop - jpi loop -``` - -This snippet would enter an infinite loop by jumping to itself on every cycle. - -The `@address ` statement isn't used in the above snippet. It allows the programmer to insert instructions or data in a specific absolute location, which is required when defining the vector table in the `bootstrap` module, for example. - -The assembler will notice if multiple program segments try to write to the same location in memory. Only one word can exist at a given address, so the assembler will abort as it does not know how to proceed. There exists a directive pair called `@begin_override` and `@end_override` that disable this behaviour and instead use the most recent definition, discarding the older one. This is useful e.g. for defining custom ISRs in the vector table, overriding the default no-op routines. - -### Syntax highlighting - -The ATK16 monorepo contains the source code for an ATK16 assembly language syntax highlighting VS Code extension in the `atk16_syntax` directory. Follow the README in the directory for installation instructions. - -## Standard library and built-ins - -The **ATK16** assembler ships with a number of built-in assembly program modules that you can include in your program. You can use the following syntax to include a built-in module. - -```lisp -@include %std_mem -``` - -Here, `std_mem` is the name of the module. The percentage sign tells the assembler to look for the module in the built-ins directory. - -### `bootstrap` - -The `bootstrap` module is a recommended entrypoint for user programs written for the ATK16. It handles all the necessary ceremony to set up the stack, the vector table and other boilerplate things that you as the programmer would otherwise have to do manually. To use it, simply use `@include %bootstrap` as the first statement in your main program module, and then define a label called `main` that the bootstrap module will jump to when it's done. - -**TBD.** - -## Links - -- [Github](https://github.com/jantuomi/ATK16) diff --git a/content/projects/_index.md b/content/projects/_index.md index 22f6291..592c762 100644 --- a/content/projects/_index.md +++ b/content/projects/_index.md @@ -7,7 +7,7 @@ insert_anchor_links: none # My projects -### [ATK16](atk16) +### [ATK16](atk16/) A 16-bit computer and ecosystem project with a custom ISA and toolchain. @@ -15,7 +15,7 @@ A 16-bit computer and ecosystem project with a custom ISA and toolchain. A dungeon synth / dark ambient duo project with Juuso. -### [Cookbook](cookbook) +### [Cookbook](cookbook/) An online cookbook of recipes I have prepared and enjoyed. Useful for me mostly. @@ -23,10 +23,10 @@ An online cookbook of recipes I have prepared and enjoyed. Useful for me mostly. A CLI tool for communicating with a [Desmofylakas API](https://github.com/poksiala/desmo-api). A kind of a half-humorous attempt at creating something that is like a small Kubernetes but for BSD jails. -### [Diddle](diddle) +### [Diddle](diddle/) A minimalist, performant, accessibility-focused Doodle alternative. See [post](/posts/diddle-event-scheduler). -### [Learning Japanese](learning-japanese) +### [Learning Japanese](learning-japanese/) I have been self-studying Japanese on and off since I was 13. diff --git a/content/projects/atk16/block-diagram.svg b/content/projects/atk16/block-diagram.svg new file mode 100644 index 0000000..138d7a1 --- /dev/null +++ b/content/projects/atk16/block-diagram.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/content/projects/atk16/index.md b/content/projects/atk16/index.md new file mode 100644 index 0000000..4219b95 --- /dev/null +++ b/content/projects/atk16/index.md @@ -0,0 +1,627 @@ +--- +title: ATK16 +weight: 0 +extra: + kind: project +--- + +> This is a live document describing the current state of the **ATK16** project. It is not final and will most likely never be. Follow the [note archive](../archive) RSS feed to be notified when significant changes to the project are introduced. + +- [Abstract](#abstract) +- [Namesake](#namesake) +- [Processor architecture](#processor-architecture) + - [CPU and the instruction cycle](#cpu-and-the-instruction-cycle) + - [Registers](#registers) + - [Arithmetic-logic unit (ALU)](#arithmetic-logic-unit-alu) +- [Memory](#memory) + - [ROM](#rom) + - [RAM](#ram) + - [MMIO segment](#mmio-segment) +- [Interrupts](#interrupts) +- [Picture and sound](#picture-and-sound) + - [Text processing unit (TPU)](#text-processing-unit-tpu) + - [Picture processing unit (PPU)](#picture-processing-unit-ppu) + - [Audio processing unit (APU)](#audio-processing-unit-apu) +- [Application binary interface (ABI)](#application-binary-interface-abi) + - [Instruction set](#instruction-set) + - [Calling convention](#calling-convention) + - [Data type representation](#data-type-representation) +- [Toolchain](#toolchain) + - [Automated tests](#automated-tests) +- [Assembly language](#assembly-language) + - [Statements](#statements) + - [`@include` and `@use`](#include-and-use) + - [`@data` and `@let`](#data-and-let) + - [`@label` and `@address`](#label-and-address) + - [Syntax highlighting](#syntax-highlighting) +- [Standard library and built-ins](#standard-library-and-built-ins) + - [`bootstrap`](#bootstrap) +- [Links](#links) + +## Abstract + +**ATK16** is a homegrown faux-retro 16-bit computer and ecosystem project that includes + +- a non-pipelined, Von Neumann style, register-machine, central processing unit (CPU) design +- a small, custom instruction set architecture (ISA) with 4-bit opcodes, i.e. 16 possible instructions +- on-board peripherals (memory, graphics and sound processors) +- memory-mapped input and output (MMIO) for communicating with on-board peripherals as well as external peripherals such as the keyboard +- 16-bit data and address buses +- a big-endian byte order +- an assembly language +- an assembler for building software for the system +- an application binary interface (ABI), including a function calling convention and data memory representations, designed with the system's features in mind +- a software emulator for running the built software on your development machine +- a register transfer logic (RTL) model designed in [Digital](https://github.com/hneemann/Digital) capable of running the built software + +{{ fig(src="block-diagram.svg", alt="ATK16 block diagram") }} + +The idea for the project hatched from my admiration for [Ben Eater](https://eater.net/) and his DIY CPU and video card projects. I was enamoured by the elegance of his systems and simply wanted to do something similar. I had none of the required knowledge for this kind of low-level CPU design work, but I had studied digital electronics for a bit in university. + +The technical details are an amalgam of the features of home computers and game consoles of the 80s and 90s, without all the optimization and smart engineering. The idea of **ATK16** is _not_ to create a performant, useful system. The idea is to learn and make something that runs programs and makes bleeps and bloops. + +The project started off as just the CPU design, but grew little by little to encompass also peripheral circuits as well as the development toolchain and software libraries. The scope defined in this document is approaching the final scope asymptotically. Nothing major has been added for some time and I'd like to keep it that way. + +As of right now, **ATK16** is capable of running text mode graphical programs with keyboard input in both the emulator and the simulator model. Sound and sprite graphics are still underway. My dream is that some day I have a big program, be it a dungeon-crawler game or some productivity software, that uses all the features available in the system in unison, and I can use that for demoing the thing. + +The spec undulates in time based on what I deem possible or necessary for the system's design. And by spec I mean a categorical product of what I have implemented thus far and what I'm planning to implement in the future. There is no written spec. In fact, this document is possibly the closest thing there is to a written spec. + +## Namesake + +ATK, or _automaattinen tietojenkäsittely_, is a Finnish term that translates to "automated information processing". The acronym is no longer used; it has been supplanted by the English acronym _IT_ even in Finnish contexts. Being now a historical expression, the term has gained a certain affect. It's like talking about automobiles instead of cars, or telephoning someone instead of calling them. There is also a bit of awe embedded in the expression: it takes effort to understand _ATK_. + +Talking about _ATK_ in current day invokes that little bit of mystique about not fully understanding how computer technology works or is supposed to work. It can also be used ironically: IT systems that malfunction, are non-optimal or just generally bad can be described as _ATK_ in Finnish techie vernacular. + +For me personally, _ATK_ brings to mind a yellowed plastic box that's running something like UNIX System V and some kind of corporate bookkeeping software. Entirely dull but somehow intriguing at the same time. + +**ATK16** is ATK with a 16-bit bus width. + +## Processor architecture + +The processor consists of the central processing unit (CPU), the arithmetic-logic unit (ALU), the register bank and the memory unit, all connected by the data bus to each other as well as onboard peripherals such as the sound and graphics cards, as well as external peripherals like the keyboard. The processor's purpose is to run user-written programs by executing instructions that are stored in memory. + +### CPU and the instruction cycle + +The heart of the system is the CPU. In classical Von Neumann style, its heartbeat consists of three stages: + +- **Fetch**: pull the next instruction from memory to the instruction register (IR) +- **Decode**: split the instruction into the operation code (opcode) part and argument parts +- **Execute**: convert the opcode into a series of control signals that operate on the arguments of the instruction, moving them between memory, registers, the arithmetic-logic unit (ALU) and the peripherals using the data bus, effectively executing the operation + +The CPU is not pipelined, which means that only one instruction is being processed at once. This makes the instruction cycle and the CPU state really easy to reason about. It also induces a big performance hit: potential amortized performance is reduced to around a third. + +Pipelining would allow work to be done in parallel, but also opens the door to jump hazards and other possible issues that have to be taken care of. + +There is also only one core; no multicore parallelism for you. + +### Registers + +The register bank consists of 8 _general use registers_ `RA`–`RH`. These are the registers that are usually being referred to when using the word _register_. + +There are also _special registers_: + +- a program counter (PC) for keeping track of the program execution position +- a memory address register (MAR) for storing the memory address being accessed +- an instruction register (IR) for storing the instruction that is being executed +- a flag register (FR) for storing the ALU flags that are updated on each ALU operation +- an interrupt program counter register (IPC) for storing the program counter value when entering an interrupt service routine (ISR) +- ...as well as other, unnamed registers that are irrelevant for understanding the operation of the system. + +Special registers can not be accessed with the same instructions that access the general use registers. + +Most instructions in the ISA have to do with moving data between these registers, the ALU and the memory. Take for example this assembly program: + +```clojure +ldi 0x100 RA ; load the value 0x100 to register RA +ldr RA RB ; read from memory address in RA (0x100) and store the value in RB +addi RB 1 RC ; add the immediate value 1 to the value in RB and store the result in RC +``` + +> 🙋🏼 Note: throughout this document, I will be using assembly language examples anachronistically to demonstrate system features even though you, the reader, might not yet know how to read it. You can check out later parts of the document for a detailed description of the assembly language if you prefer learning that way, although I'll try to write my examples in such a way that the language is comprehensible in context even without knowing the syntax or the semantics. + +Using simple operations, data is being moved from the instruction arguments to a register, from the register to the MAR, from the MAR to the memory, from the memory to another register, from the register to the ALU, from the ALU to the FR and another register. + +In addition, there exists the notion of _memory registers_, which are special memory addresses that simulate a hardware register. They exist because they can be accessed via regular memory reads and writes; no purpose-built opcodes need to be added. Most of the memory registers are MMIO registers. Accessing MMIO registers allows the user to access peripherals and do actions such as put pixels to the graphics cards or read key code values from the keyboard. + +> Note: `RH` is conventionally reserved to be used as a stack pointer (SP), so that leaves 7 registers for general use. Note that nothing in the hardware requires a stack pointer or mandates its use. However, a stack is a nice thing to have, and reserving a hardware register for the stack pointer (instead of a memory address) makes stack operations a lot faster. The `bootstrap` assembly module assumes that `RH` is the stack pointer. + +### Arithmetic-logic unit (ALU) + +ATK16 has an 8-operation ALU that takes in two values and produces a third and sets a bunch of flags. The two input values can come either from + +- register X and register Y, or +- register X and an immediate value IMM between 0 and 7. + +The output goes to register Z. + +> X, Y and Z are general use registers `RA`–`RH`, selected by the instruction arguments. + +For example, this 16-bit bitstring represents an instruction that sums `RB` and `RC` and saves the result in `RD`: + +```clojure +CCCC TTT LLL RRR SSS +0000 011 001 010 000 +``` + +Here, + +- `CCCC` is the 4-bit opcode `ALR` (arithmetic-logic with register), +- `TTT` is the 3-bit target register `RD`, +- `LLL` is the 3-bit left hand side register `RB`, +- `RRR` is the 3-bit right hand side register `RC`, and +- `SSS` is the 3-bit ALU code for the operation `L + R`. + +The full list of ALU operations is below. + +| ALU code | Operation | +| :------- | :-------- | +| 0 | L + R | +| 1 | L - R | +| 2 | L and R | +| 3 | L or R | +| 4 | L xor R | +| 5 | L >> R | +| 6 | L >>> R | +| 7 | L << R | + +> The difference between `L >> R` and `L >>> R` is that the first one is a _logical right shift_, meaning that the new bits at the most significant end of the word are always set to zero. The second one is an _arithmetic right shift_, meaning that the new bits all match the most significant bit of the original word. This is useful in signed arithmetic, where a right shift should result in a negative result (most significant bit = 1) when applied on a negative number. + +Keep in mind that the operands are always 16-bit. Any result that cannot be represented with 16 bits will over or underflow to fit. In such cases, a flag is set to signal that the arithmetic result is not correct in a real world sense. + +After each ALU operation, the flag register (FR) is updated with the new flags. The flags are as follows: + +| Flag | Interpretation | +| :----------- | :------------------------------------------------ | +| Carry (C) | In unsigned arithmetic, the result is not correct | +| Overflow (O) | In signed arithmetic, the result is not correct | +| Zero (Z) | The result is zero | +| Sign (S) | In signed arithmetic, the result is negative | + +The ALU assumes two's complement negative numbers, which makes it possible to use the same operations for both unsigned and signed arithmetic. As a user you have to choose which one you're using, and interpret the ALU flags and the result value correspondingly. + +## Memory + +The CPU is connected to the onboard memory unit. The memory unit consists of multiple components that are accessed through a single address bus. The address bus width, i.e. the size of the address, is 16 bits. This means that the largest address that can be represented is `2^16 - 1 = 0xFFFF`. In other words, there are 64K addresses. + +The memory consists of a couple of major segments: the ROM, the RAM and the MMIO segment. + +| Segment | From | To | +| :------------------------- | :----- | :----- | +| Read-only memory (ROM) | 0x0000 | 0x7FFF | +| Random access memory (RAM) | 0x8000 | 0xE7EF | +| -- Stack | 0x8000 | X | +| -- Heap | X | 0xE7EF | +| MMIO segment | 0xE7F0 | 0xFFFF | +| -- MMIO registers | 0xE7F0 | 0xE7FF | +| -- Sprite memory (PPU) | 0xE800 | 0xFFFF | +| -- Text memory (TPU) | 0xF800 | 0xFFFF | + +Let's look at each of these in detail. + +> The X between the stack and the heap represents some dynamic point that they both grow toward. If the stack and heap overlap, weird things and data loss may occur. + +### ROM + +The ROM is a memory that contains constant data and program instructions. The ROM can not be written to during runtime, only during development using tools external to the **ATK16** system. + +The ROM is initialized with a ROM image that is produced by the ATK16 assembler. The assembler takes in your assembly and produces a binary image exactly 64 KB in size. + +> ✍🏻 Vocabulary check: an _image_ is just a binary file full of bytes that make sense to a specific system or program. Usually images contain executable instructions, but there are also _disk images_ that contain the contents of a hard drive, byte for byte. Here we are talking about the first type of image. + +With a physical ROM (e.g. EEPROM), the ROM image would written to the ROM using a separate programmer circuit. We have an easier time with the ATK16 emulator, which simply takes in the filesystem path to the image as a command line argument and reads it directly from the file. The Digital simulator model similarly supports loading the image into the ROM component. + +On boot, the program counter (PC) is set to zero. When the machine starts up, the first instruction cycle reads from the address 0, which is the first word of the ROM image. This segment of the image is something akin to the boot sector of modern computers: its task is to set up critical things, such as the stack pointer, and then jump to the actual program segment. + +Between the boot sector and the program segment, which contains the user-supplied program code, there is the _vector table_ and the _data segment_. The vector table is a sequence of important, spec-defined values located at predefined addresses. + +> For example, at address `0x14` you should find the value `0x8000`. This is the address of the lowest stack address, and the beginning of the stack segment. + +The vector table needs to exist in a low address segment, since the operation `ldi` (load immediate), which is the only way to load values from instruction arguments to registers, can only load `2^9 = 512` distinct immediate values, starting from zero. Loading larger values with `ldi` requires you to first store the value in a convenient low address (i.e. `0x1FF` or less), and then load that with `ldr` (load from address in register). + +Large spec-defined values exist in the vector table and user defined values exist in the _data segment_. The data segment is dynamically sized and grows with each word of data stored in it. You can use the `@data` assembler directive to install a new word of data into the data segment. + +### RAM + +The RAM is a read-and-write memory that contains both the stack and the heap. The stack is a "hardware level" thing: there are instruction mnemonics (`spu` and `spo`) for manipulating the stack. The stack grows upwards toward larger addresses. + +> 🙋🏼 Side note: the mnemonics `spu` and `spo` are actually macros and not primitive instructions on the ATK16. Macros are a way of constructing compound mnemonics that are expanded to their primitive constituent parts during assembly. + +The RAM is full of scrambled, uninitialized gibberish at startup. The first instructions in the boot sector are responsible for setting up the stack pointer in register `RH` and pointing it to the beginning of the RAM and the stack segment, i.e. `0x8000`. + +The heap is a "software level" thing. There is nothing in the assembler that indicates the existence of a heap, or a heap allocator for that matter. The heap is purely defined in software. You can have a running ATK16 without a heap and instead just have static allocations and a big chunky stack. There is a standard library module for setting up a heap allocator though; you don't have to invent one yourself. The heap grows downwards toward smaller addresses. + +> Note that you can technically forgo the stack as well, but that's kind of wild. + +### MMIO segment + +The _memory-mapped input and output_ (MMIO) segment is a part of the address space that is concerned with accessing and manipulating peripherals and special registers. Some of the addresses in this segment are read-only, some are write-only, and some may be read-write. Running a non-supported read or write operation results in gibberish data or unexpected behaviour. + +In the beginning of the MMIO segment there is a small sliver of address space that houses the MMIO registers. + +| Address | Name | Description | +| :------ | :-------------- | :------------------------------------------------------------------------------- | +| 0xE7F0 | `terminal_addr` | Write-only, write characters to the terminal peripheral | +| 0xE7F1 | `keyboard_addr` | Read-only, read key codes from the keyboard peripheral | +| 0xE7F2 | `gr_mode_addr` | Write-only, set the graphics mode (0 = disabled, 1 = text mode, 2 = sprite mode) | +| 0xE7F3 | `iset_addr` | Write-only, set the critical section flag (see [Interrupts](#interrupts)) | + +The MMIO segment continues with the _sprite and text memories_. These are separate memories and not a part of the main RAM. Note that the sprite and text memory address spaces overlap: this is fine, because the two are never used at the same time. Sprite memory is an auxiliary memory in the picture processing unit (PPU) and text memory is an auxiliary memory in the text processing unit (TPU). The graphics mode determines which is producing a video signal to the VGA peripheral. See the [Picture and sound](#picture-and-sound) section for more detail. + +## Interrupts + +The ATK16 is a single-tasking machine, but there is still one mode of concurrency: interrupts. Interrupts are a mechanism for pausing the current execution and running a bit of special program code called an _interrupt service routine_ (ISR) instead, when a time-sensitive event occurs. When the ISR finishes, the CPU restores the paused program and continues where it left off, as if nothing happened in the meantime. + +To store and restore the program state, the interrupt handler uses special instructions to move the program counter (PC) value into and out of the interrupt program counter (IPC) register. + +--- + +So what causes interrupts? The most common cause would be a keyboard key press. On key press, the keyboard module sets an _interrupt line_ from 0 to 1. This is referred to as an _interrupt request_ (IRQ). There are four interrupt lines available for different peripherals to use. These are not configurable: a keyboard always uses `IRQ0`. The rest are reserved for future designs. + +At the beginning of each instruction cycle, a component of the CPU called the _interrupt handler_ checks if any of the IRQs are set, and prioritizes the one with the lowest index. If there is no IRQ active, the CPU continues normally. If there is an active IRQ, the handler overrides the IR and runs a special instruction to save the PC to IPC as well as read the new PC value from the vector table. Each interrupt line has an entry in the vector table that tells the system where in memory to find the corresponding ISR code. The PC is updated with this address and execution continues from the beginning of the ISR. + +The ISR code has some obligations. It is the ISR’s responsibility to leave the register bank seemingly untouched when control is returned back to the main program. To do this, all register values that the ISR erases by using the register for its own logic must be saved on the stack, and restored before returning. This is similar to the function calling convention, where functions are obligated to clean up after themselves. + +--- + +Interrupts are a source of concurrency since IRQs might be serviced at any point in the program, possibly leading to data inconsistency issues. + +Consider this scenario: a main program does three things in a loop: + +1. it reads a value (`n`) from memory into a register, +2. it increments it by one (`n + 1`), and +3. it stores it back in the same memory address. + +An interrupt pauses the execution right after the memory load (between steps 1 and 2). The ISR decrements the same value in memory (`n - 1`) and returns to the main program. The main program is now in an unexpected state: the loaded value in the register is one more than the one in memory (`n > n - 1`). Continuing forward, the program increments the register value by one (`n + 1`) and stores the result in memory. The value in memory effectively jumps from `n - 1` to `n + 1`. So much for incrementing by one. It's as if the ISR never decremented the value in the first place. + +This is called a _race condition_: the effect of a program depends on the timing of concurrent tasks. To avoid race conditions, a section of code can be declared a _critical section_. A critical section can not be interrupted. If an interrupt request happens to come in during the execution of a critical section, it stays active but control won't be transferred to the ISR. When the critical section ends, pending interrupt requests are immediately processed. + +All parts of your program that access the same data as your ISRs should always be declared critical sections! + +> Note: upon entering an ISR, the system itself toggles the critical section flag. This is done to make sure that only one interrupt can be running at once. Concurrent interrupt requests are serviced sequentially in index priority order. + +Interrupts caused by events in physical peripherals are called _hardware interrupts_. Some computers also support software interrupts, i.e. interrupts triggered from program code. ATK16 does not have these per se, but it does support setting the critical section flag from software via an MMIO register. This is used to declare a critical section in program code. + +## Picture and sound + +Video and audio are the primary means of providing feedback to the user about what is happening in the machine. The video part is handled by the two onboard graphics processors: the TPU and the PPU. + +On startup, the _graphics mode_ memory register is set to disabled (0). To activate the TPU, set the value to _text mode_ (1), and to activate the PPU, set the value to _sprite mode_ (2). + +### Text processing unit (TPU) + +The TPU is a graphics processor that produces a 40 x 30 character VGA output signal. + +{{ fig(src="text-mode.png", alt="A screenshot of a text mode program displaying a work-in-progress hardware monitor") }} +The TPU has 3 major components: + +- The character memory (ROM) +- The text memory (RAM) +- The VGA signal generator + +The character memory is a bit of ROM memory that is loaded with a _character memory image_. This is a small binary image that contains pixel data that allow the VGA signal generator to draw a character based on its character code. The image can be generated with the `charmem.py` tool. The character set used in the TPU contains 256 8x8 glyphs. + +The text memory is a block of RAM that contains character codes. One character of text can be represented in one 8-bit byte of space. For ease of implementation, each 40 character row is represented by `2^6 = 64` words. + +> This is obviously wasteful, since we're losing 24 words to undisplayable data every row. We could even pack two characters into one word, in total reducing the size of the text memory to less than 50%! +> +> These are improvements that might be implemented later, but for now, I'm focusing on getting the thing working. + +You can write into the text memory using regular memory instructions with the MMIO addresses from `0xF800` upwards. + +The VGA signal generator is a circuit that tirelessly iterates over the character and text memories, synthesizing their contents to generate pixel data for each pixel of the 320x240 VGA display. + +### Picture processing unit (PPU) + +The name and the design for the PPU are shamelessly stolen from the Nintendo Entertainment System (NES). + + + +**TBD**. + +### Audio processing unit (APU) + +**TBD**. + +## Application binary interface (ABI) + +The ABI is a platform specified set of details that describe the interface between two executable binary program modules. These could be for example a compiled application and a compiled library in a modern computer. The details of the ABI include things like how to call functions and how to represent data in memory. + +In **ATK16**, there are never two separate binary objects that need to interface each other, since all source modules supplied to the assembler are compiled into a single output image. There are no build units, no linking stage, and no libraries to load. Everything is assembled into a single image. Without two things interfacing, the term _application binary interface_ is admittedly a bit weird, but I still use it because it is commonly used to talk about the details that I want to talk about in this section. + +> Build units and a linking stage are mostly a historical byproduct of development systems that could not hold the entire build context in memory at the same time. Building nontrivial projects was made possible by allowing the compiler to work on only a little bit at a time. This is useful even today when building very large projects. +> +> When compiling for the **ATK16**, this issue is nonexistent. Any modern system has enough RAM to build a max size, 64KB ROM image. + +### Instruction set + +The instruction set is _minimum instruction set computer_ (MISC) inspired. It is not truly minimal; multiple instructions, for example `jpr` ja `brr` could probably be replaced by elaborate multi-jump schemes. However, it is still quite small, and the opcodes fit nicely in 4 bits (16 available values). + +| Opcode | Mnemonic | Description | Arguments | +| :----- | :------- | :------------------------------ | :------------------------------ | +| 0000 | alr | Arithmetic-logic with register | left reg, right reg, target reg | +| 0001 | ali | Arithmetic-logic with immediate | left reg, imm value, target reg | +| 0010 | ldr | Load from address in register | address reg, target reg | +| 0011 | str | Store to address in register | value reg, address reg | +| 0100 | ldi | Load immediate | immediate value | +| 0101 | jpr | Jump to address in register | address reg | +| 0110 | jpi | Jump to immediate address | imm address | +| 0111 | brr | Branch to address in register | imm flag selector, address reg | +| 1000 | bri | Branch to immediate address | imm flag selector, imm address | +| 1001 | lpc | Load program counter | target reg | +| 1010 | – | – | – | +| 1011 | – | – | – | +| 1100 | isrp0 ¹ | ISR process 0 | – | +| 1101 | isrp1 ¹ | ISR process 1 | – | +| 1110 | rti | Return from interrupt | – | +| 1111 | hlt | Halt | – | + +> ¹ `isrp0` and `isrp1` are not intended to be used in program code. They are "magic" instructions that override the current instruction when servicing an interrupt request. See [Interrupts](#interrupts). + +All instructions have a fixed 16-bit width. 4 bits are reserved for the opcode and 12 bits are reserved for the argument data. + +Register arguments use a 3-bit selector value to pick one of the 8 general use registers `RA`–`RH`. Immediate arguments are either 3 bits wide (`ali`) or 9 bits wide (`ldi`, `jpi`, `bri`). + +Citing the [ROM](#rom) section: + +> The vector table needs to exist in a low address segment, since the operation `ldi` (load immediate), which is the only way to load values from instruction arguments to registers, can only load immediate values up to `2^9 - 1 = 511`. Loading larger values with `ldi` requires you to first store the value in a convenient low address, and then load that with `ldr` (load from address in register). + +This limitation gives rise to a common pattern where larger values are stored in low addresses in the data segment, and then loaded to registers with a combination of `ldi` and `ldr`. + +Unconditional jumps (`jpi`, `jpr`) do what it says own the label: they jump to the given address without checking any condition. Conditional jumps, or branches (`bri`, `brr`) use a 2-bit flag selector to check if a specific flag register (FR) flag is set (see ALU), and only then do the jump. If the specified flag is not set, the branch instruction is a no-op. + +In general, ATK16 uses absolute addressing everywhere, except in the immediate jumps (`jpi`, `bri`), which use relative addressing (`result = PC + address`). This is useful because most of the immediate jumps are short distance and can be represented in 9 bits if using relative addressing, but not absolute addressing. Long jumps must use a `ldi` + `ldr` + `jpr` pattern, which is based on the `ldi` + `ldr` pattern that is used to read large values to registers. + +For more detail on how arguments are packed into the instruction word, see source code of the `ucode.py` command line tool. + +### Calling convention + +To call a function, the caller must set the first argument to `RA`, the second to `RB`, etc. Naturally, a maximum of seven (7) arguments can be passed, since `RH` is the stack pointer by spec. The caller then pushes the return address `PC + 1` to the stack and jumps to the function address. + +> If you need to pass more than 7 arguments, consider passing a pointer to a struct instead. + +The callee must make sure that the registers contain the same values at return time as they did at call time, with the exception of the return value register, which is defined as `RG` by spec. To do this, the called function must save and restore any registers it uses in its body. + +> The idea here is that register values are locally consistent. All register manipulations should be explicit in program code. If a function call would be allowed to clobber registers (other than `RG` which is allowed to be clobbered), it would be difficult to reason about the local behaviour of a program. + +The mechanical parts of this calling convention are implemented by the `calli`/`callr` and `return` built-in macros. The calling user only needs to set up arguments in the correct registers and read the return value from the return value register. + +--- + +Note that function calls are similar to interrupts in multiple ways: + +| Function call | Interrupt | +| :-------------------------------------------------------------- | :-------------------------------------------------- | +| The return address `PC + 1` is saved on the stack | The PC is loaded to the IPC register | +| The PC is replaced by the function address | The PC is replaced by the ISR address | +| The called function is responsible for restoring register state | The ISR is responsible for restoring register state | +| The function returns by jumping to the return address | The ISR returns with the instruction `rti` | + +### Data type representation + +> This is an area of active development. + +ATK16 has a canonical memory representation for primitive data types like integers and booleans, as well as some compound data types like strings and structs. The standard library expects data to be stored using these representations. Users are free to define custom memory representations for new types and implement procedures to access and manipulate them in their applications. + +All data types defined here align with the 16-bit word boundary. + +| Type | Bits | Representation | +| :--------------------------------- | :---------------- | :-------------------------------------------------- | +| Unsigned integer | `16` | Binary number | +| Signed integer | `16` | Two's complement, binary number | +| Character | `16` | 8 bits padding, 8 bits of character code | +| Boolean | `16` | Zero for `false`, any other bitstring for `true` | +| Fixed-point fraction with base _b_ | `16` | Binary integer (base only known in compile time) | +| Array of _n_ elements of type _a_ | `n x sizeof(a)` | Consecutive bytes / words in memory | +| String of _n_ characters | `16 + n x 16` | Pascal string, i.e. the length followed by the data | +| Struct with fields _a0_, _a1_, ... | `sum(sizeof(aN))` | Consecutive field representations in memory | + +> 🙋🏼 Future note: A new packed string representation might happen in the future. In a packed string, two 8-bit characters are stored in one 16-bit word. This would mean that the 16-bit word alignment rule is relaxed to 8-bit byte alignment string-internally. The string representation as a whole is still padded to the word boundary. + +## Toolchain + +The **ATK16** project is a combination of designs and implementations. In addition to the Digital simulator model and the emulator, which are implementations of the system itself, there are also a number of command line tools. The table below summarizes the major directories available in the ATK16 monorepo. + +| Name | Description | +| :---------------------------- | :---------------------------------------------------- | +| atk16_emu | Software emulator | +| atk16_asm | Assembler | +| digital_diagrams | Digital logic simulator model | +| atk16_syntax | VSCode syntax highlighting for ATK16 assembly | +| atk16_utils | CLI tools to generate the character memory image etc. | +| atk16_projects | Example application projects | +| test | Automated tests (`make test`) | +| \* atk16_bytecode_compiler | Python -> ATK16 assembly compiler | +| \* atk16_ast_walking_compiler | Python -> ATK16 assembly compiler | + +> \* ) These are more of an academic experiment in compilers and not a serious component of the ATK16 toolchain. The compilers are not implemented to a working degree and only support a very limited subset of Python 3. It turns out that while trying to compile a language that is totally not meant to be used as a systems language is a fun thing attempt, alas, it does not lead to a fruitful outcome. + +All programmed tools are written in reduced dependency Python 3. The only external dependencies are `getch` and `pygame`, which are used for unbuffered keyboard input, and audio and video, respectively. + +The easiest way to get something running is to use the emulator. Follow the steps below to get the `monitor` application booted up. + +1. `git clone` the ATK16 repository +2. Set up a suitably modern Python 3 environment and install the ATK16 toolchain as a local Python package: + +```sh +$ pip install . +``` + +3. Assemble the `monitor.atk16` source into a ROM image: + +```sh +$ atk16c resources/asm/monitor.atk16 -o out/monitor.bin +``` + +> The assembler also produces another file in the output directory called `out/monitor.bin.dbg` that contains debug symbols. The step debugger uses these to map instruction words to their original source lines. + +4. Load the built image into the emulator and boot it up: + +```sh +$ atk16emu out/monitor.bin +``` + +You can supply the `-d` flag to the emulator to start the program in step debugger mode. Press `?` to see usage instructions. + +```sh +$ atk16emu -d out/monitor.bin +``` + +### Automated tests + +If you plan to make changes to **ATK16** components you can run the test suite by running the following in the monorepo root: + +```sh +$ make test +``` + +These tests utilize some useful utilities that can be useful when writing tests for your own application code as well. See the test source code in the `test` directory for details. + +## Assembly language + +Assembly languages are a family of low-level programming languages whose statements strongly correspond with the machine code instructions of their target platform. _ATK16 assembly_ is an assembly language designed to be understood by the ATK16 assembler and compiled into a ROM image for ATK16 to read and execute. + +The language aims to be as human-readable as an assembly language realistically can. To aid in this, the assembler supports _macros_, which allow the programmer to construct named reusable blocks of code that are expanded to primitive instructions at assembly time. Using macros has no effect on performance when compared to writing out instructions by hand. + +Here's a code snippet: + +```lisp +@use monitor_macros:* + +@include %bootstrap +@include %std_mem +@include %std_term +@include %std_bump_alloc + +;; Constants +@data text_buffer_size 2048 +@data input_buffer_size 64 + +;; Global variable pointers +;; 0xE800 - 0xEFFF: sprite buffer space that is unused in this program +@data text_cursor_p 0xE800 +@data input_buffer_p 0xE801 +@data input_cursor_p 0xE802 + +@data title_string " »» ATK16 monitor v0.1 »»" +@data subtitle_string " Run [help] for a list of commands" +@data prompt_string " > " + +@label main +;; initialize heap allocator + calli bump_reset + +;; initialize global variables +;; text_cursor_p = text_mem + ldi text_cursor_p RA + ldr RA RA + ldi vt_text_mem RB + ldr RB RB + str RB RA +``` + +Let's dissect this. + +> 🙋🏼 The pandoc-based code highlighting that you're seeing on this page naturally does not support ATK16 assembly. I try to use highlighting for other programming languages to get some sort of readability. + +### Statements + +Statements are sequences of code delimited by a newline. Other types of whitespace (i.e. not newlines) separate terms in a statement. Statements that start with `@` are called directives. Comments start with a semicolon `;` and end at a newline character or EOF. + +Indented statements are _instructions_ or _constant values_. The indentation is only a convention that makes it easier to visually separate directives from the rest of the program. Non-indented instruction or constant value statements are permitted but are considered unconventional. + +An instruction starts with a mnemonic (e.g. `ldi`) that is either a primitive instruction (see [Instruction set](#instruction-set)) or a macro. Macros are either user-defined or built in to the assembler (such as `calli`). Constant value statements are just numeric values. + +These are all valid statements: + +```lisp + 0xFF + 0b1 + 10 + ${ord("A")} ; evaluate Python expr to value 65 + jpi main ; jump to label "main" + bri zero branch ; jump to label "branch" if zero flag is set +``` + +A numeric constant must be representable in 16 bits. + +Symbols `RA`–`RH`, `carry`, `overflow`, `zero` and `sign` are defined for convenience and evaluate to numeric values `0`–`7`, `0`, `1`, `2` and `3` respectively. + +You can use `${ ... }` to evaluate an arbitrary Python expression as part of a statement. + +### `@include` and `@use` + +At the top we see some _assembler directives_: `@include` and `@use`. These directives are similar in spirit: they include other code into the assembly context. `@include file` injects the contents of the file `file.atk16` at the location of the directive. File names prepended with a `%` sign are [Builtin modules](standard-library-and-built-ins). For you C and C++ programmers, there is an implicit `#pragma once` in every module: including a file is idempotent. Even if you `@include` a file multiple times, only a single copy of the contents will be assembled into the output image. + +You can use `@use file:*` to import macros from a Python module called `file.py`. Macro modules are Python files that define a dictionary called `extensions` that maps symbol names to functions that returns a list of lists of statement terms. Here's a snippet from the `monitor_macros.py` file that the above assembly code uses: + +```python +from atk16_asm.asm_ops import * + +expansions: dict[str, Callable[..., ExpandResult]] = {} +def register_macro(func): + expansions[func.__name__] = func + return func + +@register_macro +def m_newline(r1: str, r2: str) -> ExpandResult: +return [ + *expand_ldi("text_cursor_p", r1), + *expand_ldr(r1, r1), + *expand_ldr(r1, r2), + *expand_slri(r2, "6", r2), # cursor = cursor / 64 + *expand_addi(r2, "1", r2), # cursor = cursor + 1 + *expand_slli(r2, "6", r2), # cursor = cursor * 64 + *expand_str(r2, r1), +] +``` + +There is only one namespace and no scoping in ATK16 assembly. If you `@include` or `@use` a resource in one file, its contents may become visible to other files as well, depending on the inclusion tree traversal order. Library developers are instructed to prefix all symbols with the library name so that the probability of a namespace clash is reduced. Although, since all assembly is done from source code only, you can fix namespace conflicts with a simple search-and-replace. + +> However, Python macros `@use`’d in an `@include`’d assembly module are never visible in the parent includer context. This is because `@include` creates a separate assembler context that does not leak its bindings to the parent context. + +### `@data` and `@let` + +Next, you can see some `@data` directives. These are used to inject constant value bindings to the _data segment_, which is a location in memory whose starting address is determined with the `@data_segment` directive. The `bootstrap` module, which is the recommended system entrypoint for most programs, defines a data segment that can be used in user programs. + +A constant binding `@data X ` injected into the data segment is available in the rest of the program as if it was defined with a label directive: `@label X`. The data is represented in memory according to the [Data type representation](#data-type-representation) spec. Currently, only integer and string values are supported. + +`@let X Y` can be used to define symbol bindings that only exist during assembly time. This is useful for giving name to otherwise magic numbers. Referencing the symbol `X` simply does an environment lookup and evaluates to `Y`. + +### `@label` and `@address` + +The `@label main` statement defines a label called `main` that points to the address in ROM that contains the instruction or datum on the following line. Labels are a way to refer to a specific memory location without knowing its absolute address. Labels can be used in most places where a numeric value is expected. The most common use case is as the target of a jump instruction: + +```clojure +@label loop + jpi loop +``` + +This snippet would enter an infinite loop by jumping to itself on every cycle. + +The `@address ` statement isn't used in the above snippet. It allows the programmer to insert instructions or data in a specific absolute location, which is required when defining the vector table in the `bootstrap` module, for example. + +The assembler will notice if multiple program segments try to write to the same location in memory. Only one word can exist at a given address, so the assembler will abort as it does not know how to proceed. There exists a directive pair called `@begin_override` and `@end_override` that disable this behaviour and instead use the most recent definition, discarding the older one. This is useful e.g. for defining custom ISRs in the vector table, overriding the default no-op routines. + +### Syntax highlighting + +The ATK16 monorepo contains the source code for an ATK16 assembly language syntax highlighting VS Code extension in the `atk16_syntax` directory. Follow the README in the directory for installation instructions. + +## Standard library and built-ins + +The **ATK16** assembler ships with a number of built-in assembly program modules that you can include in your program. You can use the following syntax to include a built-in module. + +```lisp +@include %std_mem +``` + +Here, `std_mem` is the name of the module. The percentage sign tells the assembler to look for the module in the built-ins directory. + +### `bootstrap` + +The `bootstrap` module is a recommended entrypoint for user programs written for the ATK16. It handles all the necessary ceremony to set up the stack, the vector table and other boilerplate things that you as the programmer would otherwise have to do manually. To use it, simply use `@include %bootstrap` as the first statement in your main program module, and then define a label called `main` that the bootstrap module will jump to when it's done. + +**TBD.** + +## Links + +- [Github](https://github.com/jantuomi/ATK16) diff --git a/content/projects/atk16/text-mode.png b/content/projects/atk16/text-mode.png new file mode 100644 index 0000000..93c2752 Binary files /dev/null and b/content/projects/atk16/text-mode.png differ diff --git a/content/projects/diddle.md b/content/projects/diddle.md deleted file mode 100644 index 1fab603..0000000 --- a/content/projects/diddle.md +++ /dev/null @@ -1,25 +0,0 @@ ---- -title: Diddle, a minimalist event scheduler -weight: 0 -extra: - kind: project ---- - -I don't like Doodle. It pushes ads and its premium subscription relentlessly while simultaneously feeling like a clumsy, lumbering beast. There is also a lot to be desired in terms of accessibility and mobile-friendliness. - -There are alternatives, like [dudle](https://dud-poll.inf.tu-dresden.de/) or [framadate](https://framadate.org/abc/en/). I could probably make do with those. However, to me this felt like an opportunity to spin up a quick MVP. This MVP turned into _diddle_. - -Diddle ([live instance](https://diddle.jan.systems/), [Github](https://github.com/jantuomi/diddle)) is a minimalist tool that focuses on fast load times, accessibility and pragmatism. The feature set is based on my own personal requirements. It works great for quickly filling out a poll on mobile. - -> **Note**: The instance hosted on my domain might go down any second. Use it at your own discretion. - -{{ fig(src="/files/diddle_screenshot.png", alt="Diddle screenshot showing an answerable poll") }} - -The first 80% of the project was done in two evenings. The last 80% was done during the next week. The tech stack is the _get-shit-done_ stack: **Python** + **Flask** + **PostgreSQL**. There is no required client side javascript: all client side logic is strictly for [progressive enhancement](https://developer.mozilla.org/en-US/docs/Glossary/Progressive_Enhancement). You can use "dumb" browsers like w3m or lynx to enjoy Diddle just fine. - -Check out the README on Github for more info. - -## Links - -- [Github](https://github.com/jantuomi/diddle) -- [Live instance on diddle.jan.systems](https://diddle.jan.systems) diff --git a/content/projects/diddle/diddle_screenshot.png b/content/projects/diddle/diddle_screenshot.png new file mode 100644 index 0000000..437567e Binary files /dev/null and b/content/projects/diddle/diddle_screenshot.png differ diff --git a/content/projects/diddle/index.md b/content/projects/diddle/index.md new file mode 100644 index 0000000..7b51de5 --- /dev/null +++ b/content/projects/diddle/index.md @@ -0,0 +1,25 @@ +--- +title: Diddle, a minimalist event scheduler +weight: 0 +extra: + kind: project +--- + +I don't like Doodle. It pushes ads and its premium subscription relentlessly while simultaneously feeling like a clumsy, lumbering beast. There is also a lot to be desired in terms of accessibility and mobile-friendliness. + +There are alternatives, like [dudle](https://dud-poll.inf.tu-dresden.de/) or [framadate](https://framadate.org/abc/en/). I could probably make do with those. However, to me this felt like an opportunity to spin up a quick MVP. This MVP turned into _diddle_. + +Diddle ([live instance](https://diddle.jan.systems/), [Github](https://github.com/jantuomi/diddle)) is a minimalist tool that focuses on fast load times, accessibility and pragmatism. The feature set is based on my own personal requirements. It works great for quickly filling out a poll on mobile. + +> **Note**: The instance hosted on my domain might go down any second. Use it at your own discretion. + +{{ fig(src="diddle_screenshot.png", alt="Diddle screenshot showing an answerable poll") }} + +The first 80% of the project was done in two evenings. The last 80% was done during the next week. The tech stack is the _get-shit-done_ stack: **Python** + **Flask** + **PostgreSQL**. There is no required client side javascript: all client side logic is strictly for [progressive enhancement](https://developer.mozilla.org/en-US/docs/Glossary/Progressive_Enhancement). You can use "dumb" browsers like w3m or lynx to enjoy Diddle just fine. + +Check out the README on Github for more info. + +## Links + +- [Github](https://github.com/jantuomi/diddle) +- [Live instance on diddle.jan.systems](https://diddle.jan.systems) diff --git a/static/files/2024-wines.jpg b/static/files/2024-wines.jpg deleted file mode 100644 index 81cddef..0000000 Binary files a/static/files/2024-wines.jpg and /dev/null differ diff --git a/static/files/2025_wrapped_genres.jpeg b/static/files/2025_wrapped_genres.jpeg deleted file mode 100644 index 630be8a..0000000 Binary files a/static/files/2025_wrapped_genres.jpeg and /dev/null differ diff --git a/static/files/3-2-1-Backup-Rule.png b/static/files/3-2-1-Backup-Rule.png deleted file mode 100644 index ed6b675..0000000 Binary files a/static/files/3-2-1-Backup-Rule.png and /dev/null differ diff --git a/static/files/aliexpress_minipc.png b/static/files/aliexpress_minipc.png deleted file mode 100644 index 0cb57a4..0000000 Binary files a/static/files/aliexpress_minipc.png and /dev/null differ diff --git a/static/files/aliexpress_minipc_results.png b/static/files/aliexpress_minipc_results.png deleted file mode 100644 index 9242e3e..0000000 Binary files a/static/files/aliexpress_minipc_results.png and /dev/null differ diff --git a/static/files/atk16-block-diagram.svg b/static/files/atk16-block-diagram.svg deleted file mode 100644 index 138d7a1..0000000 --- a/static/files/atk16-block-diagram.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/static/files/atk16-text-mode.png b/static/files/atk16-text-mode.png deleted file mode 100644 index 93c2752..0000000 Binary files a/static/files/atk16-text-mode.png and /dev/null differ diff --git a/static/files/aws-exam-drink.jpeg b/static/files/aws-exam-drink.jpeg deleted file mode 100644 index 16f8933..0000000 Binary files a/static/files/aws-exam-drink.jpeg and /dev/null differ diff --git a/static/files/backdoor_feature_done.jpeg b/static/files/backdoor_feature_done.jpeg deleted file mode 100644 index a9c71d5..0000000 Binary files a/static/files/backdoor_feature_done.jpeg and /dev/null differ diff --git a/static/files/backdoor_feature_wip.jpeg b/static/files/backdoor_feature_wip.jpeg deleted file mode 100644 index 2f381d1..0000000 Binary files a/static/files/backdoor_feature_wip.jpeg and /dev/null differ diff --git a/static/files/cassette_player.jpeg b/static/files/cassette_player.jpeg deleted file mode 100644 index 6591cc4..0000000 Binary files a/static/files/cassette_player.jpeg and /dev/null differ diff --git a/static/files/diddle_screenshot.png b/static/files/diddle_screenshot.png deleted file mode 100644 index 437567e..0000000 Binary files a/static/files/diddle_screenshot.png and /dev/null differ diff --git a/static/files/diffuser_1.jpg b/static/files/diffuser_1.jpg deleted file mode 100644 index d5e6693..0000000 Binary files a/static/files/diffuser_1.jpg and /dev/null differ diff --git a/static/files/diffuser_2.jpg b/static/files/diffuser_2.jpg deleted file mode 100644 index f8c8b84..0000000 Binary files a/static/files/diffuser_2.jpg and /dev/null differ diff --git a/static/files/diffuser_3.jpg b/static/files/diffuser_3.jpg deleted file mode 100644 index 5efa8ab..0000000 Binary files a/static/files/diffuser_3.jpg and /dev/null differ diff --git a/static/files/do-june-billing.png b/static/files/do-june-billing.png deleted file mode 100644 index a38f71c..0000000 Binary files a/static/files/do-june-billing.png and /dev/null differ diff --git a/static/files/dopamine_office.jpeg b/static/files/dopamine_office.jpeg deleted file mode 100644 index aad4e65..0000000 Binary files a/static/files/dopamine_office.jpeg and /dev/null differ diff --git a/static/files/eurobsdcon_2025_binders.jpeg b/static/files/eurobsdcon_2025_binders.jpeg deleted file mode 100644 index ee02077..0000000 Binary files a/static/files/eurobsdcon_2025_binders.jpeg and /dev/null differ diff --git a/static/files/eurobsdcon_2025_cevapi.jpeg b/static/files/eurobsdcon_2025_cevapi.jpeg deleted file mode 100644 index 67595b4..0000000 Binary files a/static/files/eurobsdcon_2025_cevapi.jpeg and /dev/null differ diff --git a/static/files/eurobsdcon_2025_group.jpeg b/static/files/eurobsdcon_2025_group.jpeg deleted file mode 100644 index b94c739..0000000 Binary files a/static/files/eurobsdcon_2025_group.jpeg and /dev/null differ diff --git a/static/files/eurobsdcon_2025_hallway.jpeg b/static/files/eurobsdcon_2025_hallway.jpeg deleted file mode 100644 index f15217a..0000000 Binary files a/static/files/eurobsdcon_2025_hallway.jpeg and /dev/null differ diff --git a/static/files/fish-and-chips-duke-of-argyll.jpg b/static/files/fish-and-chips-duke-of-argyll.jpg deleted file mode 100644 index a9fefc8..0000000 Binary files a/static/files/fish-and-chips-duke-of-argyll.jpg and /dev/null differ diff --git a/static/files/hinokodo-static-abyss.png b/static/files/hinokodo-static-abyss.png deleted file mode 100644 index edfac67..0000000 Binary files a/static/files/hinokodo-static-abyss.png and /dev/null differ diff --git a/static/files/indieweb_presentation.png b/static/files/indieweb_presentation.png deleted file mode 100644 index e9d0096..0000000 Binary files a/static/files/indieweb_presentation.png and /dev/null differ diff --git a/static/files/internet_surf.png b/static/files/internet_surf.png deleted file mode 100644 index 7787770..0000000 Binary files a/static/files/internet_surf.png and /dev/null differ diff --git a/static/files/jojo-in-snow.webm b/static/files/jojo-in-snow.webm deleted file mode 100644 index 19047e5..0000000 Binary files a/static/files/jojo-in-snow.webm and /dev/null differ diff --git a/static/files/kenrokuen.jpg b/static/files/kenrokuen.jpg deleted file mode 100644 index f739d82..0000000 Binary files a/static/files/kenrokuen.jpg and /dev/null differ diff --git a/static/files/london-from-above.jpg b/static/files/london-from-above.jpg deleted file mode 100644 index a7762ed..0000000 Binary files a/static/files/london-from-above.jpg and /dev/null differ diff --git a/static/files/miru_gameplay.jpeg b/static/files/miru_gameplay.jpeg deleted file mode 100644 index 51d8d44..0000000 Binary files a/static/files/miru_gameplay.jpeg and /dev/null differ diff --git a/static/files/miru_time_to_kill_triple.jpg b/static/files/miru_time_to_kill_triple.jpg deleted file mode 100644 index 97be7fe..0000000 Binary files a/static/files/miru_time_to_kill_triple.jpg and /dev/null differ diff --git a/static/files/muumimusiikkia.jpeg b/static/files/muumimusiikkia.jpeg deleted file mode 100644 index a99d8f6..0000000 Binary files a/static/files/muumimusiikkia.jpeg and /dev/null differ diff --git a/static/files/old-man-yells-at-cloud.png b/static/files/old-man-yells-at-cloud.png deleted file mode 100644 index bf6a0f3..0000000 Binary files a/static/files/old-man-yells-at-cloud.png and /dev/null differ diff --git a/static/files/pursotin_fastfetch.png b/static/files/pursotin_fastfetch.png deleted file mode 100644 index 241f5e9..0000000 Binary files a/static/files/pursotin_fastfetch.png and /dev/null differ diff --git a/static/files/pursotin_network.svg b/static/files/pursotin_network.svg deleted file mode 100644 index 79d1dea..0000000 --- a/static/files/pursotin_network.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/static/files/pursotin_raidz.svg b/static/files/pursotin_raidz.svg deleted file mode 100644 index d2942ac..0000000 --- a/static/files/pursotin_raidz.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/static/files/repo_langs.png b/static/files/repo_langs.png deleted file mode 100644 index 9c85b3b..0000000 Binary files a/static/files/repo_langs.png and /dev/null differ diff --git a/static/files/reveal-js-logo.png b/static/files/reveal-js-logo.png deleted file mode 100644 index f7b671f..0000000 Binary files a/static/files/reveal-js-logo.png and /dev/null differ diff --git a/static/files/samurai_garden.jpeg b/static/files/samurai_garden.jpeg deleted file mode 100644 index f6637b0..0000000 Binary files a/static/files/samurai_garden.jpeg and /dev/null differ diff --git a/static/files/taulu_koira.jpeg b/static/files/taulu_koira.jpeg deleted file mode 100644 index 11c4f32..0000000 Binary files a/static/files/taulu_koira.jpeg and /dev/null differ diff --git a/static/files/valokate_1.jpg b/static/files/valokate_1.jpg deleted file mode 100644 index 1ddccf2..0000000 Binary files a/static/files/valokate_1.jpg and /dev/null differ diff --git a/static/files/valokate_2.jpg b/static/files/valokate_2.jpg deleted file mode 100644 index 3d66088..0000000 Binary files a/static/files/valokate_2.jpg and /dev/null differ diff --git a/static/files/villa.jpeg b/static/files/villa.jpeg deleted file mode 100644 index 51cc84b..0000000 Binary files a/static/files/villa.jpeg and /dev/null differ diff --git a/static/files/wagyu_grill.jpeg b/static/files/wagyu_grill.jpeg deleted file mode 100644 index 0e835bc..0000000 Binary files a/static/files/wagyu_grill.jpeg and /dev/null differ diff --git a/static/files/wikimedia_monad.svg b/static/files/wikimedia_monad.svg deleted file mode 100644 index 887c3c6..0000000 --- a/static/files/wikimedia_monad.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file -- cgit v1.3