<?xml version="1.0" encoding="UTF-8"?>
<?xml-stylesheet type="text/xsl" href="feed.xsl"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
  <channel>
    <title>Jorvik Software — Notes</title>
    <link>https://jorviksoftware.cc/notes</link>
    <description>Development notes and technical write-ups from Jorvik Software.</description>
    <language>en-gb</language>
    <lastBuildDate>Sun, 02 Aug 2026 17:02:37 GMT</lastBuildDate>
    <atom:link href="https://jorviksoftware.cc/notes/feed.xml" rel="self" type="application/rss+xml"/>
    <item>
      <title>Announcing Citadel: My Latest Native Mac Game</title>
      <link>https://jorviksoftware.cc/notes/2026/08/02/announcing-citadel-my-latest-native-mac-game</link>
      <guid isPermaLink="true">https://jorviksoftware.cc/notes/2026/08/02/announcing-citadel-my-latest-native-mac-game</guid>
      <pubDate>Sun, 02 Aug 2026 17:22:52 GMT</pubDate>
      <description><![CDATA[<div class="full-width"><img src="https://jorviksoftware.cc/notes/2026/08/02/title.jpg" alt="The title screen of Citadel, my macOS first-person shooter"></div>
<p class="image-caption">Citadel &mdash; the boot-up terminal, and the logo of a company you are not yet supposed to be suspicious of</p>

<p><a href="https://jorviksoftware.cc/games/citadel">Citadel</a> is out.</p>
<p>It is a first-person shooter for the Mac, in the <a href="https://en.wikipedia.org/wiki/Doom_(1993_video_game)"><em>Doom</em></a> idiom, and it is the ground war of the story that started with <a href="https://jorviksoftware.cc/games/strataris">Strataris</a>. That game was the air war &mdash; you flew low over six colony worlds and held the sky against a marauder fleet for as long as you could. You did not hold it long enough. This is what happened next.</p>
<p>The swarm landed. The colonies of Demeter, Tantalus, Boreas, Pandora, Vulcan and Vesper went dark, one after another. Everyone who could get underground got underground, and then something followed them down. You spent all of it in a medbay bunk with a head injury, which is the only reason you&rsquo;re alive and the reason you remember none of it &mdash; and it leaves you, when you finally stand up, as the only trained soldier still standing down there.</p>
<p>So you go down there on foot. Forty-eight floors. Six citadels, eight levels each, all the way to the bottom of every one of them.</p>
<p>It&rsquo;s free, like everything I make. It&rsquo;s public domain, source and all. And I am, if I&rsquo;m honest, prouder of it than of anything else I&rsquo;ve built.</p>
<h2>The rule that changes everything</h2>
<p>Here is the single design decision the whole game hangs off, and it took me an embarrassingly long time to realise it was the good one.</p>
<p>Every floor is held by a <strong>Queen</strong> &mdash; a rooted, enormous brood-mother who births fresh drones for as long as she&rsquo;s alive, hates your headlamp, and sits between you and the freight elevator. Only the plasma rifle wounds her. Fine. That&rsquo;s a boss fight; you&rsquo;ve had a hundred of those.</p>
<p>Except when she goes down, she isn&rsquo;t dead. She&rsquo;s <em>down</em>. And if you walk away, she gets back up, and the floor never ends.</p>
<p>To finish her, you have to walk up to the body and <strong>burn it</strong>. Flamethrower. Close range. Standing over the thing you just killed while its drones are still coming, holding a trigger, watching a fuel gauge that only tops up at a drum somewhere else on the floor.</p>
<p>That one rule reorganises the entire game underneath it. Fuel stops being a weapon and starts being a <em>budget</em> &mdash; every gout you spend on a drone in a corridor is fuel you won&rsquo;t have for the thing that actually matters. The plasma rifle and the flamethrower stop being alternatives and become two halves of one job. And the fight has a coda: there&rsquo;s no clean moment of victory where the music swells and you walk out. There&rsquo;s a body, and a chore, and it has to be done properly.</p>
<p>I didn&rsquo;t design that from first principles. I stumbled into it while trying to solve a completely different problem, played it once, and knew immediately not to touch it again.</p>
<div class="full-width"><img src="https://jorviksoftware.cc/notes/2026/08/02/fire.jpg" alt="The flamethrower firing in Citadel, a drone caught in the stream of burning fuel"></div>
<p class="image-caption">The flamethrower is a genuine volume of burning fuel &mdash; it blooms, it rolls, it goes sooty, and it lights the room as it goes</p>

<h2>Two castes, three weapons, and a lot of arithmetic</h2>
<p>The colony holds one organism in two forms. The <strong>drone</strong> is a slick green-black melee hunter &mdash; slow, but it is not stupid. It comes in numbers, it comes around corners, and it will happily let you commit to the one in front while two more work their way down the flank. It has to reach you to hurt you, which sounds generous until three of them have you in a doorway. And the <strong>Queen</strong> sits at the heart of the floor making more of them for as long as you leave her alive.</p>
<p>Against that you carry the plasma rifle and the flamethrower permanently, and you&rsquo;ll find a rocket launcher out in the field. The rifle is the workhorse and the only thing that wounds a Queen; it will nudge a shot onto a nearby target for you, but the cone is narrow and deliberately unhelpful &mdash; it is an assist, not an autopilot. The rockets hit hard enough that standing too close to your own blast is a real and recurring way to die. And you carry proximity mines, which you can walk over and the swarm cannot, which is the entire point of a mine.</p>
<p>Everything is finite. Every plasma cell, every litre of fuel, every rocket you&rsquo;re carrying. What keeps it from being miserable is that the colony itself is the supply line: shoot a fuel drum and it ruptures into a bomb, or stand beside it and it refills your flamethrower. Plug into a wall-mounted recharge socket and it tops up your cells &mdash; and sockets slowly rebuild their own reserve, so a floor you&rsquo;ve stripped bare will have a little something for you if you come back through it later. You are never permanently starved. You are, permanently, doing sums.</p>
<div class="full-width"><img src="https://jorviksoftware.cc/notes/2026/08/02/combat.jpg" alt="Combat in Citadel: a drone approaching past a fuel drum, seen through the helmet visor HUD"></div>
<p class="image-caption">A drone comes over the fuel drum. Shoot the drum, or shoot the drone.</p>

<h2>All light is man-made</h2>
<p>You are underground for the entire game. There is no daylight, no skybox, no ambient glow from nowhere in particular. Every single photon in Citadel comes from something you could point at: a bulkhead lamp, a flickering terminal, the sick green self-light of a nukage pool, the fire you just started, and the lamp on your own helmet.</p>
<p>That sounds like a technical note. It is actually the whole atmosphere. The upper floors of a citadel run bright &mdash; the fixtures still work up there, mostly. The deeper you go, the more of them are out, until by the bottom of a building you are essentially carrying the only light in the world and the corners are genuinely, unhelpfully black. The lowest floors are flooded, so you wade, and you can hear yourself doing it.</p>
<p>There is no status bar. I tried one; it made the game feel like a spreadsheet with monsters. So the 3-D view fills the entire screen and every readout is projected onto the inside of your <strong>helmet visor</strong> instead &mdash; vitals, weapon pods, a live cells/fuel/mines telemetry strip, and an <em><a href="https://en.wikipedia.org/wiki/Aliens_(film)">Aliens</a></em>-style <strong>motion tracker</strong> with an objective caret riding on its rim. The caret points at the living Queen. Once she&rsquo;s ash, it swings around and points at the exit. You never need to open a menu to know what the floor still wants from you.</p>
<p>I will not pretend the debt to <a href="https://en.wikipedia.org/wiki/Ridley_Scott">Ridley Scott</a> isn&rsquo;t enormous. Wet corridors, a crew that didn&rsquo;t make it, a corporation whose logo is on everything and whose intentions you should probably examine, and a thing in the dark that is better at this than you are. <em><a href="https://en.wikipedia.org/wiki/Alien_(film)">Alien</a></em> is the reason I find industrial corridors frightening, and <em>Aliens</em> is the reason I find a chirping motion tracker frightening, and I have taken both entirely without shame.</p>
<h2>I told you it was all equations. I was wrong.</h2>
<p>In <a href="https://jorviksoftware.cc/notes/2026/07/05/i-tore-out-the-renderer-again">July I wrote a post</a> explaining, at some length and with obvious pleasure, that this game had no models in it. Not one. The world was a <strong>signed distance field</strong> &mdash; every wall, every pipe, every rusted surface and every monster described as a mathematical function, sphere-traced on the <abbr data-title="Graphics Processing Unit">GPU</abbr>, a few hundred thousand times a frame. A corridor is an equation. I was thrilled with myself.</p>
<p>Reader, I tore it out (I know, it&rsquo;s becoming a habit).</p>
<p>It was about seventeen hundred lines of Metal compute kernel, it was the cleverest code in the project, and by the middle of July it had lost an argument to a boring old <strong>triangle rasteriser</strong> on both counts that matter: the raster path <em>looked better</em> and it <em>cost less</em>. Not one or the other. Both. Raymarching a distance field is gorgeous for organic, grown-looking things and quietly miserable at sharp machined edges &mdash; and it turns out a colony built by an industrial mining concern is mostly sharp machined edges. Meanwhile every additional creature on screen was more maths per pixel per frame, and the frame budget on a real-time renderer is 16.7 milliseconds and it does not negotiate.</p>
<p>So the kernel is gone. Deleted. The level is built into vertex buffers once when it loads, materials are still shaded procedurally in code, and the whole thing is lit in HDR, bloomed and tonemapped on the way out. The only trace of the Raymarcher that remains is a file called <span class="lock">GPURaymarch.swift</span>, which no longer raymarches anything and survives purely as the home of some shared types I couldn&rsquo;t be bothered to rehouse. It sits there like a pub that kept its name after the brewery closed.</p>
<p>I mention this partly because it&rsquo;s honest, and partly because it&rsquo;s the second time in two games I&rsquo;ve deleted a renderer I loved. There may be a pattern. I am choosing not to examine it.</p>
<h2>The four things I let in</h2>
<p>Strataris shipped with <strong>no asset files at all</strong> &mdash; no textures, no audio, no fonts, no models, the entire game generated in code and small enough to fit on a floppy disk. It remains the constraint I&rsquo;m proudest of.</p>
<p>Citadel breaks those constraints in exactly four places, and I want to be straight about all of them, because I&rsquo;ve been loudly pure about this before and it would be pretty cheap to go quiet now.</p>
<p><strong>The bodies.</strong> The drone and the Queen ship as 3-D meshes with baked colour maps. Organic, ridged, biomechanical anatomy reads better sculpted than generated, and after weeks of trying I couldn&rsquo;t make maths produce something that looked like it had <em>grown</em>. So yes &mdash; this game ships textures. Two of them are monsters.</p>
<p><strong>Three hero plates.</strong> Cinematic images behind the front-end screens, the title among them.</p>
<p><strong>Five imported models, geometry only.</strong> A dormitory bunk and four pieces of plant machinery, downloaded, decimated to budget, and then stripped of every material they arrived with and re-surfaced in code. The shapes are borrowed. The surfaces are mine.</p>
<p><strong>Audio.</strong> This one I&rsquo;ve <a href="https://jorviksoftware.cc/notes/2026/07/02/the-sound-i-could-not-fake">already confessed to at length</a>. You can synthesise a gun. You cannot synthesise a living thing, and a game like this lives or dies on the noise something makes when it notices you. So Citadel ships a small, curated set of CC0 and CC-BY samples &mdash; all properly credited, all free for me to give away and for you to have. But the code still does most of the work: the Queen&rsquo;s snarls are pitched down because she&rsquo;s <em>enormous</em> and randomly nudged each time so she never repeats herself, and a drone two rooms away comes to you muffled and drowned in reverb, as though it&rsquo;s carrying through a hundred metres of steel. There&rsquo;s no soundtrack either &mdash; just a stack of layers and a live signal measuring how much trouble you&rsquo;re in, thickening as things close and thinning when you&rsquo;re safe.</p>
<p>And the synthesiser I built for Strataris? Still in the binary. It got demoted to the safety net: if a sample is ever missing, it quietly generates a stand-in so the game never falls silent. The old religion is on night shift.</p>
<h2>Forty-eight floors, and every one of them proven</h2>
<p>Levels are generated fresh every time you play &mdash; rooms, corridors, doors, coloured keycards, hidden secrets, hazard-walled fuel stores, an exit you have to earn. Play it twice, you&rsquo;ll never see the same building twice.</p>
<p>Which raises the question that will keep you awake: <em>what if it builds one you physically cannot finish?</em> Random doesn&rsquo;t care about you. Give it enough rolls and it will happily lock the red key inside the room that needs the red key, hand it to you with a straight face, and let you conclude I shipped a broken game.</p>
<p>So the generator doesn&rsquo;t get the final say. Before you ever set foot on it, a solver floods the floor from your starting position &mdash; reach, collect a key, unlock what that key opens, reach further &mdash; and if it can&rsquo;t touch the exit, the whole level goes in the bin and it tries again. I <a href="https://jorviksoftware.cc/notes/2026/07/06/every-level-is-winnable-and-i-can-prove-it">wrote about this in July</a>, back when I&rsquo;d only just discovered that the <em>furniture</em> can quietly ruin a certified-solvable floor by parking an oil drum in a doorway like a cork in a bottle. That&rsquo;s covered now too. You will die on these floors. You will never be trapped on one.</p>
<p>Clearing a floor has a small ritual attached, and I&rsquo;m fond of it. You call the freight elevator, the leaves part, and you step into the car &mdash; and there&rsquo;s a panel in there that totals up what you did on the floor you&rsquo;re leaving: kills, secrets, score. Then it takes you down. Eight of those and the building is yours, at which point you teleport to the next planet and start again at the top of a fresh one, brighter at the surface, worse further down, with the difficulty scaled to how deep you are rather than how long you&rsquo;ve been playing.</p>
<p>You can save whenever you like, into any of ten named slots. A save is about three hundred bytes, because it stores the <em>checkpoint</em> &mdash; the state you arrived on this floor with &mdash; and nothing else. The map isn&rsquo;t written down; it&rsquo;s regenerated from the run&rsquo;s seed when you load, and the lift rides you back down to it. You always resume on a clean floor start, never stranded half way through a fight you were losing.</p>
<div class="full-width"><img src="https://jorviksoftware.cc/notes/2026/08/02/dark.jpg" alt="A dark corridor in Citadel, lit only by a bulkhead lamp and the marine's helmet lamp"></div>
<p class="image-caption">The deeper you go, the fewer fixtures still work</p>

<h2>The quiet things</h2>
<p>The parts I like best are the ones the game never mentions.</p>
<p>A <strong>sick bay</strong> always holds a med-kit, and standing in one slowly closes your wounds &mdash; ten health every thirty seconds you stay. A room the <strong>nukage</strong> has reached does exactly the reverse at exactly the same rate, pool or no pool, because the air in there is the hazard. Neither is announced. There&rsquo;s no icon, no chime, no tutorial pop-up. The only tell is your own health quietly moving, and working that out for yourself is the whole point.</p>
<p><strong>Use a computer desk</strong> and the game zooms into an amber <abbr data-title="Cathode Ray Tube">CRT</abbr>. Most of them are jokes. Three of them aren&rsquo;t: three interactive, paged documents that tell the same slow catastrophe from three different angles &mdash; a Director&rsquo;s log, a stack of corporate memos, and a young scientist&rsquo;s emails home to her mum. Read all three and you&rsquo;ll know considerably more about why this happened than the marine does. Nobody makes you read any of it. There&rsquo;s also a fully playable <em>Space Invaders</em> on one of those terminals, with its own persistent high-score table, because of course there is.</p>
<p>And somewhere in the run there are <strong>seven bodies</strong>. Colonists who sealed themselves into rooms when it all went wrong, and have been lying there a very long time. One pool of seven, dealt out across the six citadels, never more than two to a building &mdash; so a full campaign is the only way to find them all. The game does not tell you they exist, does not mark them, and does not congratulate you when you find one. The only thing you can do for them is the flamethrower, and it&rsquo;s worth a thousand points a body and fifty thousand for all seven, awarded in complete silence.</p>
<p>I thought about putting a notification on that. I&rsquo;m glad I didn&rsquo;t.</p>
<h2>What it cost</h2>
<p>Strataris took five months. Citadel took <strong>six weeks</strong> (give or take) &mdash; first commit on the twenty-second of June, released today. A hundred and ninety commits, thirty thousand lines of Swift, one renderer deleted, one rule about pure code broken in four places, and one stretch in late July where the same two test frames kept coming out differently on every run and I genuinely began to doubt my own arithmetic. It was Swift seeding its dictionary hashing per process, so two tied walls kept swapping places. I found it by fingerprinting every field of the generator and bisecting, after two days of <em>reasoning</em> about it got me (and even the <abbr data-title="Artificial Intelligence">AI</abbr>) precisely nowhere. Measure, don&rsquo;t reason. I keep having to relearn this.</p>
<p>It runs on Apple silicon only, and that&rsquo;s deliberate rather than lazy: it&rsquo;s a real-time Metal renderer tuned to a 16.7 ms frame, every measurement behind that tuning was taken on an M-series Mac, and an Intel slice would have shipped without ever once running on the hardware it claimed to support. I&rsquo;d rather say no than ship a maybe.</p>
<div class="full-width"><img src="https://jorviksoftware.cc/notes/2026/08/02/hall.jpg" alt="The Hall of the Fallen &mdash; Citadel's persistent high-score table, listing name, score, depth and colony"></div>
<p class="image-caption">The Hall of the Fallen &mdash; name, score, how deep you got, and which colony took you</p>

<h2>Go and get them out</h2>
<p><a href="https://jorviksoftware.cc/games/citadel">Citadel</a> is out now. It&rsquo;s <strong>free</strong> &mdash; properly free, public domain, no catch, no telemetry, no account, no launcher. I&rsquo;ve <a href="https://jorviksoftware.cc/notes/2026/04/09/why-everything-is-free">written before about why everything I make is free</a> so regular readers know the drill by noe, except to say that I built this because I wanted it to exist and now it does, and you should have it.</p>
<p>You&rsquo;ll need an Apple silicon Mac running macOS 14 or later. There&rsquo;s an installer and a zip, both signed and notarised, and the source is on GitHub if you&rsquo;d like to see how any of it works or take it apart for parts.</p>
<p>Take the plasma rifle to the Queen. Take the flamethrower to what&rsquo;s left of her. Get your name into the Hall of the Fallen.</p>
<p>And keep an eye on the light on your helmet. It&rsquo;s the only one down there that&rsquo;s definitely still working.</p>
<p>P.S: It plays best with a gamepad, it&rsquo;s damn hard with just the keyboard.</p>
]]></description>
    </item>
    <item>
      <title>Changelog: MenuTidy and ActiveSpace</title>
      <link>https://jorviksoftware.cc/notes/2026/07/27/changelog-menutidy-and-activespace</link>
      <guid isPermaLink="true">https://jorviksoftware.cc/notes/2026/07/27/changelog-menutidy-and-activespace</guid>
      <pubDate>Mon, 27 Jul 2026 20:07:40 GMT</pubDate>
      <description><![CDATA[<div class="photo-frame"><img src="https://jorviksoftware.cc/notes/changelog-hero.jpg" alt="A hand ticking items off a checklist."></div>
<p class="image-caption">Photo by <a href="https://unsplash.com/@jakubzerdzicki">Jakub Żerdzicki</a> on <a href="https://unsplash.com/photos/yKnIbJV0RbY">Unsplash</a></p>

<p>Most of what I do to these little menu-bar apps never earns a headline. A control that was hard to see. A feature someone asked for. A thing macOS quietly broke on a Tuesday. This is a roundup of the recent small stuff across two of them &mdash; <a href="https://jorviksoftware.cc/utilities/menutidy">MenuTidy</a> and <a href="https://jorviksoftware.cc/utilities/activespace">ActiveSpace</a> &mdash; and the short stories behind each change, because the <em>why</em> is usually more interesting than the <em>what</em>.</p>
<h2>MenuTidy learns to tidy itself</h2>
<p>The itch had a name before it had a feature. You expand your menu bar to grab a hidden icon, you use it, and then&hellip; it just sits there, expanded, until you remember to click it shut. MenuTidy&rsquo;s whole job is a tidy menu bar &mdash; and here it was leaving the mess out on the side.</p>
<p>Then someone opened an issue asking for exactly that: auto-collapse a few seconds after your pointer leaves the menu bar. (Thank you, fuzzy76.) I&rsquo;d been quietly wanting it myself, so in it went.</p>
<p>It started life as a hidden setting &mdash; the sort you&rsquo;d only find if you knew the incantation &mdash; while I lived with it for a while and made sure it felt helpful rather than fidgety. It did. So it&rsquo;s now a proper, first-class setting: right-click the chevron, open Settings, and there&rsquo;s an <strong>Auto-Collapse</strong> switch with a delay you can set anywhere from 0 to 999 seconds. Zero means &ldquo;the instant I move away&rdquo;; a few seconds means &ldquo;give me a beat to change my mind.&rdquo; The countdown resets if you move back up to the bar, so it only tidies once you&rsquo;ve genuinely wandered off.</p>
<p>It&rsquo;s off by default &mdash; nothing changes unless you decide you want it.</p>
<h2>When Tahoe moved the furniture</h2>
<p>MenuTidy has a feature for notched Macs called <strong>Reveal Hidden Icons</strong>: the menu-bar icons that get swallowed by the notch, gathered into a little panel you can click. fuzzy76 came back with a puzzle &mdash; clicking an icon in that panel did nothing. Except sometimes it did. First click: dead. Every click after: fine.</p>
<p>&ldquo;Works on the second try&rdquo; is the kind of bug that makes you distrust your own eyes. It turned out not to be my code at all &mdash; it was macOS 26, Tahoe. The trick MenuTidy uses to &ldquo;click&rdquo; a hidden icon is a little accessibility nudge, and on Tahoe the very first nudge to another app&rsquo;s menu-bar icon comes back with a shrug &mdash; the line&rsquo;s cold. The second one connects. So MenuTidy now warms the line: if the first nudge doesn&rsquo;t take, it quietly tries again, and the click lands the first time, every time.</p>
<p>While I was in there, I tidied the panel itself. It used to flash a cached list and then redraw with the real one &mdash; which meant, for a moment, you couldn&rsquo;t quite trust what you were looking at. Now it shows a brief spinner and then the real list, once, so what you see is always the finished article. Choosing Reveal again closes the panel; collapsing the bar closes it too.</p>
<h2>ActiveSpace: which app am I about to land on?</h2>
<p>ActiveSpace has a lovely trick &mdash; a <span class="lock"><span class="kbd">command</span><span class="kbd">tab</span></span> that shows only the apps on your <em>current</em> Mission Control space, instead of every window across every desktop. It works beautifully. But the highlight marking which app you&rsquo;d switch to was a pale, translucent thing, and on a bright wallpaper or under a bit of glare it all but vanished. You&rsquo;d tab across the row and genuinely not be sure where you&rsquo;d end up.</p>
<p>So the highlight grew up. It&rsquo;s now a filled chip in your system accent colour, with a crisp outline and a soft shadow &mdash; the same visual language macOS uses for a selected thing everywhere else. It reads at a glance on any background, and it follows whatever accent colour you&rsquo;ve chosen. No more squinting.</p>
<h2>The theme, if there is one</h2>
<p>None of these is a big feature. A switch, a retry, a brighter highlight. But this is most of what keeping software good actually <em>is</em>: listening when someone tells you what&rsquo;s annoying them, noticing when the ground shifts under you, and refusing to let &ldquo;you can just about live with it&rdquo; be the finished state. Small, careful, and ideally invisible &mdash; right up until the moment you&rsquo;d have missed it.</p>
<p>All shipping now, via the built-in updater or the download pages, whichever you prefer.</p>
]]></description>
    </item>
    <item>
      <title>When a Broken Link Is Not a Broken Link</title>
      <link>https://jorviksoftware.cc/notes/2026/07/16/when-a-broken-link-is-not-a-broken-link</link>
      <guid isPermaLink="true">https://jorviksoftware.cc/notes/2026/07/16/when-a-broken-link-is-not-a-broken-link</guid>
      <pubDate>Thu, 16 Jul 2026 18:18:34 GMT</pubDate>
      <description><![CDATA[<div class="photo-frame"><img src="https://jorviksoftware.cc/notes/2026/07/16/no-entry.jpg" alt="A white-and-red &lsquo;Do Not Enter&rsquo; sign."></div>
<p class="image-caption">Photo by <a href="https://unsplash.com/@kylejglenn">Kyle Glenn</a> on <a href="https://unsplash.com/photos/dGk-qYBk4OA">Unsplash</a></p>

<p>I keep a small dashboard running that watches this website for me. Is it up? Does the sitemap still match what&rsquo;s actually on disk? Are the update feeds live? And, once a night, the unglamorous one: are there any dead links &mdash; anywhere on the site &mdash; quietly rotting?</p>
<p>One morning it told me there was. One broken link, on one page. The page was a post I&rsquo;d written a couple of weeks earlier, <a href="https://jorviksoftware.cc/notes/2026/07/05/i-tore-out-the-renderer-again">the one about tearing out my renderer and rebuilding a whole game out of equations</a>. The dead link, it reckoned, was the one pointing at <a href="https://www.shadertoy.com/">Shadertoy</a> &mdash; which, if you read that post, you&rsquo;ll know I was practically writing a love letter to.</p>
<h2>The link worked fine</h2>
<p>I clicked it. It loaded instantly. Of course it did &mdash; Shadertoy is very much alive and the wizards are still conjuring worlds out of a screenful of maths on it every day.</p>
<p>So why did my tool insist it was broken?</p>
<p>Because my tool doesn&rsquo;t click links the way you and I do. It knocks. It sends a tiny <code>HEAD</code> request &mdash; the web&rsquo;s equivalent of tapping on the door and waiting for a &ldquo;yes, come in&rdquo; &mdash; and it reads the answer. A missing page answers <em>404, gone</em>. Shadertoy answered something different: <em>403, forbidden</em>.</p>
<p>Not &ldquo;there&rsquo;s nothing here.&rdquo; &ldquo;There&rsquo;s something here, and you may not have it.&rdquo;</p>
<p>Shadertoy, like an awful lot of the modern web, sits behind bot defences &mdash; the machinery that waves humans through and slams the door on anything that smells automated. And a link-checker knocking with no browser, no mouse, no history is <em>exactly</em> the sort of thing that machinery exists to keep out. My robot looked like a robot, got treated like one, and dutifully filed the silence under &ldquo;broken.&rdquo;</p>
<p>It wasn&rsquo;t wrong about what it saw. It was wrong about what it <em>meant</em>.</p>
<h2>The three wrong fixes</h2>
<p>There are three easy ways to make a warning like this go away, and all of them are worse than the warning.</p>
<p>The first is to delete the check for that one link. Quick, clean &mdash; and now that link is invisible forever, including the day it <em>genuinely</em> dies.</p>
<p>The second is to reach for a bigger hammer: whitelist the whole domain, or just stop checking external links at all. That throws away the entire reason I built the thing.</p>
<p>The third is the worst, and the most tempting: just&hellip; ignore it. Let the little red number sit there and learn to scroll past it. The trouble is that a warning you train yourself to ignore is worse than no warning at all, because the day it&rsquo;s telling you something real, you&rsquo;ll scroll past that one too. A smoke alarm that cries wolf gets its battery taken out &mdash; and then it&rsquo;s just a plastic disc on the ceiling.</p>
<h2>Known, and accepted</h2>
<p>What I actually wanted was to tell the tool something I knew and it couldn&rsquo;t: <em>I&rsquo;ve looked. It&rsquo;s fine. Stop counting it as a fault &mdash; but don&rsquo;t stop watching it.</em></p>
<p>So that&rsquo;s the feature. There&rsquo;s now a <strong>Mark known</strong> button on any external link that fails the probe. Press it and the link steps out of the red &ldquo;broken&rdquo; list and into a quiet one called <strong>Accepted &mdash; known good</strong>. It stops counting against the site&rsquo;s health. The nagging stops.</p>
<p>But &mdash; and this is the whole point &mdash; it does not stop being <em>checked</em>. Every nightly sweep still knocks on that door. The link just gets filed under &ldquo;yes, I know&rdquo; instead of &ldquo;something&rsquo;s wrong.&rdquo; One click puts it straight back on the naughty list the moment I change my mind, and if the link ever disappears from the site altogether, the tool tells me <em>that</em>, too &mdash; so an old judgment can&rsquo;t quietly go stale behind my back.</p>
<h2>The honest bit</h2>
<p>It is a tiny feature. A button and a list. I could describe the whole thing to you in a sentence.</p>
<p>But I think the small distinction underneath it is the part worth keeping. A false positive you silence by deleting the check is a little lie you tell yourself: <em>there was never a problem here.</em> A false positive you record a <em>judgment</em> about is something else entirely &mdash; it&rsquo;s honesty with a memory. One quietly unplugs the alarm. The other says &ldquo;I heard it, I went and looked, here&rsquo;s what I found&rdquo; &mdash; and leaves every wire exactly where it was.</p>
<p>The most dangerous state for any watchdog isn&rsquo;t being occasionally wrong. It&rsquo;s barking often enough at nothing that you stop looking up when it barks.</p>
<h2>Peace with the doormen</h2>
<p>The people behind Shadertoy conjure entire animated worlds out of a few lines of maths &mdash; the exact trick I borrowed to build a monster. It turns out they also conjure a very firm &ldquo;no&rdquo; for robots. Fair enough; it&rsquo;s their door.</p>
<p>My little watchtower and I have made our peace with it. The link isn&rsquo;t broken. I know it isn&rsquo;t. And now my tool knows that I know &mdash; without ever once agreeing to stop checking.</p>
]]></description>
    </item>
    <item>
      <title>The Volume Knob Was Winning</title>
      <link>https://jorviksoftware.cc/notes/2026/07/12/the-volume-knob-was-winning</link>
      <guid isPermaLink="true">https://jorviksoftware.cc/notes/2026/07/12/the-volume-knob-was-winning</guid>
      <pubDate>Sun, 12 Jul 2026 13:05:47 GMT</pubDate>
      <description><![CDATA[<div class="photo-frame"><img src="https://jorviksoftware.cc/notes/2026/07/12/volume.jpg" alt="Audio volume control"></div>
<p class="image-caption">Photo by <a href="https://unsplash.com/@clasiqh">clasiqh</a> on <a href="https://unsplash.com/photos/a-close-up-of-a-djs-turntable-on-a-table-XMSmfNmfHBY">Unsplash</a></p>

<p>I have been building the same music library since I was a teenager, and it shows.</p>
<p>There are pub bands on it &mdash; actual bands, in actual pubs, recorded by someone&rsquo;s cousin. There are buskers I liked enough to hunt down. There are <abbr data-title="Compact Disc">CD</abbr>s I bought off support acts at the merch table, half of whom never troubled a streaming service and never will. There is an almost-complete collection of <a href="https://en.wikipedia.org/wiki/The_KLF">The KLF</a>, a band so committed to standing alone that they deleted their own back catalogue in 1992 and set fire to a million quid for an encore &mdash; music you genuinely cannot find in a tidy, mastered, modern form anywhere, because they made sure of it.</p>
<p>I love all of it. But here is the thing about music assembled over thirty years from every corner of the world: it is recorded at every volume known to engineering. The pub band is a whisper. The remastered pop single is a wall. And when you press shuffle, they come at you one after another, at random, with no warning.</p>
<h2>Loud, quiet, loud</h2>
<p>You know the dance. A quiet track comes on, so you nudge the volume up to hear it properly. The next track is a brick-walled banger and it takes the top of your head off. Down goes the dial. Track after that is a demo someone recorded in a shed, so up it goes again. On speakers it&rsquo;s annoying. On headphones it is genuinely unpleasant &mdash; there&rsquo;s nowhere for the loud one to go but straight into your skull.</p>
<p>For years my thumb never really left the volume control. And when I got tired of that, I did the worse thing: I started <em>skipping</em>. Skipping tracks I actually liked, purely because I couldn&rsquo;t face the volume lottery of finding out how loud the next one would be. That&rsquo;s the moment that finally got me &mdash; when I realised the fiddling had quietly talked me out of listening to my own music. The volume knob was winning.</p>
<p>So I built <a href="https://jorviksoftware.cc/utilities/ballast">Ballast</a>.</p>
<h2>The thing I refused to install</h2>
<p>The obvious way to get at everything your Mac is playing is to install a <em>virtual audio driver</em> &mdash; a fake output device that intercepts the sound, hands it to an app to muck about with, then passes it on to your speakers. It&rsquo;s how the serious tools do it, and it works. It also means installing a system component you have to approve, possibly reboot for, and then live with forever, squatting in your Sound settings whether the app is running or not.</p>
<p>I did not want to ship that. A free little utility has no business re-plumbing your Mac.</p>
<p>Happily, I didn&rsquo;t have to. A while back macOS quietly grew <strong>Core Audio process taps</strong> &mdash; Apple&rsquo;s own, built-in way to read the system audio mix without any of that. Nothing to install, nothing added to your Sound settings, no driver, no router. Ballast puts a (muted) tap on the mix, does its work, and plays the result back through whatever output you were already using. Quit the app and it&rsquo;s gone without a trace, because there was never anything installed to leave behind. Your chosen output stays chosen. There is nothing to route.</p>
<p>That was the first thing I got right. Most of what followed, I got wrong at least once.</p>
<h2>False start one: the compressor</h2>
<p>My first instinct was to ride the volume live &mdash; hear it get loud, pull it down; hear it go quiet, push it up. A compressor, essentially, or an automatic gain control.</p>
<p>It works, and it sounds <em>dreadful</em>, because it flattens the music. The whole soul of a good recording is the swing from quiet to loud &mdash; the hush before the chorus, the drop, the breath. A live gain-rider squashes exactly that, and everything comes out as the same lifeless, even mush. I&rsquo;d have made my music quieter <em>and</em> worse.</p>
<p>The mistake was aiming at the wrong target. The problem was never the dynamics <em>within</em> a track. It was the mismatch <em>between</em> tracks.</p>
<h2>False start two: measuring the wrong way</h2>
<p>So: don&rsquo;t compress. <em>Normalise.</em> Work out how loud a whole track actually is &mdash; as a human ear perceives it, not as a naive peak meter sees it &mdash; and apply one gentle, fixed adjustment across the entire thing, leaving every quiet-to-loud swing completely intact.</p>
<p>Measuring perceived loudness is, thankfully, a solved problem. There&rsquo;s an international standard for it &mdash; <strong><a href="https://en.wikipedia.org/wiki/EBU_R_128">EBU R128 / ITU-R BS.1770</a></strong>, the <abbr data-title="Loudness Units relative to Full Scale">LUFS</abbr> scale that streaming services and broadcasters use to stop the adverts being louder than the programme. Same maths. Ballast steers every track toward one comfortable target and leaves its insides alone.</p>
<p>Except &mdash; and this is the wrinkle that cost me the most sleep &mdash; you cannot know a track&rsquo;s true loudness until you&rsquo;ve <em>heard</em> it, and the loudest moment might be the final chorus. You can&rsquo;t normalise the first second correctly until you&rsquo;ve heard the last.</p>
<h2>So it learns</h2>
<p>The answer was to stop pretending I could get it perfect on first listen, and to let Ballast <em>remember</em> instead.</p>
<p>The very first time it meets a track, it does the best it honestly can, live &mdash; anchored to the loudest thing it&rsquo;s heard so far, so nothing suddenly blasts you. But it&rsquo;s also <em>measuring the whole track properly as it plays</em>, and it files that reading away under the track&rsquo;s identity. The next time that track comes round, Ballast recognises it and applies one exact, fixed adjustment from the very first sample &mdash; no ramp, no guessing, no compromise. The more you listen, the more of your library it simply <em>knows</em>, and it keeps quietly re-checking its own numbers as you go, so a library assembled over thirty years slowly sorts itself out in the background while you get on with enjoying it.</p>
<p>There was one last piece of stubbornness in me &mdash; and this is the one I ended up talking myself out of. For a while, whenever Ballast met a new track, it would narrow its listening to <em>only</em> the music app while it took the measurement, walling off any stray notification that might skew the number. Clever. Also, it turned out, needless: measure a <em>whole</em> track and the odd interruption averages away to nothing, and every later play quietly corrects the figure anyway. Worse, the trick had a cost &mdash; switching what it listened to left a faint seam you could hear at the edge of every new track. So I pulled it out. The very first measurement of a track is a hair less pristine now, and not one of us will ever hear the difference. Track changes still come from the same public notifications Apple Music and Spotify broadcast, so it stays in step even across a gapless album.</p>
<h2>The honest bit</h2>
<p>I could tell you it all worked first time. It did not.</p>
<p>The first night I left it running unattended, it decided that a track fading gently to silence was a <em>broken</em> audio tap, and cheerfully rebuilt its whole audio graph eleven times in under a minute &mdash; every time a song got quiet. Teaching it the difference between a fade (a slow, graceful sink into silence) and an actual fault (an abrupt cliff to dead zero) was an entire afternoon of its own. That&rsquo;s the unglamorous reality of something meant to run silently in the background for hours and never once get in your way: most of the work is in the failures nobody will ever see.</p>
<h2>And, yes, the dancer</h2>
<p>A few days ago I wrote about <a href="https://jorviksoftware.cc/notes/2026/07/09/nobody-dances-like-that">an afternoon I spent failing to animate a dancer</a>, and I signed off by saying she lived &ldquo;at the quiet edge of a bigger project I&rsquo;m not ready to talk about yet.&rdquo;</p>
<p>This is the project. She was hiding in a loudness normaliser the whole time.</p>
<p>Ballast has a real-time music visualiser &mdash; six styles, the smoke-and-shadow Dancer among them. Because it rides the <em>same</em> audio tap that does the levelling, it reacts to anything playing, not just your music app. It started life as a debugging toy: a way to actually <em>see</em> the levels moving while I chased bugs. It got completely out of hand, as these things do, and now it&rsquo;s one of my favourite parts.</p>
<h2>Set it once</h2>
<p>Ballast is free, there&rsquo;s no telemetry, and nothing you play is ever recorded, stored, or sent anywhere &mdash; it measures your audio on your Mac, in the moment, and the only thing it ever writes down is a small list of how loud your tracks are.</p>
<p>Set the volume once. Put on the remastered single, then the pub band, then a support-act CD nobody else on earth has a copy of. Let it shuffle.</p>
<p>And take your thumb off the dial.</p>
]]></description>
    </item>
    <item>
      <title>Nobody Dances Like That</title>
      <link>https://jorviksoftware.cc/notes/2026/07/09/nobody-dances-like-that</link>
      <guid isPermaLink="true">https://jorviksoftware.cc/notes/2026/07/09/nobody-dances-like-that</guid>
      <pubDate>Thu, 09 Jul 2026 21:27:37 GMT</pubDate>
      <description><![CDATA[<div class="photo-frame"><img src="https://jorviksoftware.cc/notes/2026/07/09/dancers.jpg" alt="Ghostly dancers, soft focus, a smoke-filled dance floor."></div>
<p class="image-caption">Photo by <a href="https://unsplash.com/@cherstve_pechivo">Liana S</a> on <a href="https://unsplash.com/photos/abstract-blurred-figures-in-dark-misty-environment-lFa59ule6W4">Unsplash</a></p>

<p>I did not set out to learn anything about human beings. I set out to draw a dancer. The human beings turned up uninvited and taught me a lesson anyway, which is very much their way.</p>
<p>Here&rsquo;s the setup. A small macOS app I&rsquo;ve been chipping away at &mdash; I&rsquo;ll tell you what it actually <em>does</em> another day &mdash; grew a music visualiser almost by accident. Once it existed, I couldn&rsquo;t leave it alone. I&rsquo;d seen one of those lovely minimal audio visualisers: a lone figure moving in a hazy, backlit room, a slim waveform riding over the top. And what I wanted from it wasn&rsquo;t the waveform. It was the <em>room</em>. The presence. The sense that someone was in there, dancing, just out of focus.</p>
<p>So, obviously, I tried to draw the someone.</p>
<h2>So I drew her</h2>
<p>The <a href="https://jorviksoftware.cc/notes/2026/07/02/the-sound-i-could-not-fake">house rule</a> still applied, the one I keep for my games: nothing loaded from a file. No stock video, no motion-capture, no borrowed footage of an actual dancer. If I wanted a figure, I had to <em>build</em> one out of maths, drawn fresh on the <abbr data-title="Graphics Processing Unit">GPU</abbr> every frame.</p>
<p>And I did. A circle for a head. A soft column for a torso. Two arms on little sweeping cycles, rising and falling. A skirt that flared from the waist and rippled at the hem. She swayed. She lifted her arms. She was, unmistakably, a woman dancing.</p>
<p>She was also awful, and it took me an embarrassingly long time to work out why.</p>
<h2>The trouble with people</h2>
<p>Here is the thing nobody warns you about. The instant a shape reads as <em>human</em>, the person watching stops looking at a shape and starts watching a <em>person</em> &mdash; and everyone, without exception, is a lifelong expert on how people move. My dancer held her arms overhead a beat too long. <em>Nobody dances like that.</em> Her sway was a touch too even, too metronomic. <em>Nobody dances like that.</em> Every frame invited the same four words, and the <em>better</em> I made the figure &mdash; the more clearly human &mdash; the louder those words got.</p>
<p>I had built myself an <a href="https://en.wikipedia.org/wiki/Uncanny_valley">uncanny valley</a> and moved straight in.</p>
<p>The fix, it turned out, was not a more convincing dancer. I could have spent a month on inverse kinematics and weight-shift and secondary motion and still landed on &ldquo;good puppet.&rdquo; The fix was to stop drawing a person at all.</p>
<h2>The black blob</h2>
<p>So I abstracted her. Off came the head, the arms, the skirt, until what was left was just a soft vertical presence &mdash; a shadow in the beam, swaying. No anatomy to get wrong. Problem solved.</p>
<p>Reader, it was not solved. I had walked straight into the <em>second</em> trap.</p>
<p>A shape with soft edges is still a shape. What I had now was a flat, dead, coal-black column &mdash; a hole punched in the smoke. A blob. I stared at it. The <abbr data-title="Artificial Intelligence">AI</abbr> I do this work alongside stared at it too. We agreed, in as many words: blob.</p>
<p>The mistake was the same one, wearing a different coat. I was still drawing a <em>thing</em> &mdash; filling in a region and softening its border &mdash; and a filled region, however soft its edge, always reads as an object cut out of the world. The trick I&rsquo;d been missing all afternoon was this: don&rsquo;t draw a shape. Draw a <em>density</em>.</p>
<h2>Vapour, not silhouette</h2>
<p>The old question was <em>&ldquo;is this pixel inside the figure?&rdquo;</em> &mdash; and any yes/no question gives you an edge, and an edge is a thing to be wrong about. The new question was gentler: <em>&ldquo;how much shadow hangs in the air right here?&rdquo;</em></p>
<p>Instead of a body, a soft haze that&rsquo;s thickest along a slowly-wandering spine and fades to nothing in every direction, all of it broken up by the same procedural smoke drifting through the rest of the room. No border. No flat core. Light seeps through the thin parts and catches in the dense ones, unevenly, the way light actually behaves in a smoky room. And just like that it stopped being a blob <em>in</em> the smoke and started being smoke, with something alive moving inside it. Same maths family as <a href="https://jorviksoftware.cc/notes/2026/07/05/i-tore-out-the-renderer-again">the monsters in my game</a>, pointed at a kinder subject.</p>
<h2>Move <em>with</em> the music, not <em>at</em> it</h2>
<p>Then, motion. My first attempt jerked on every beat &mdash; twitching on each kick drum like a cheap club light with a wiring fault. Which makes sense on paper and looks terrible in practice, because a beat is a <em>hammer</em>, and a dancer is not being hammered.</p>
<p>So I stopped moving to the beat and started moving to the <em>tempo</em>: a slow, smoothed reading of how much energy is in the music right now, feeding a clock that quietly speeds up as the room gets busy and eases off when it calms. The presence drifts faster through a loud passage and lingers through a quiet one &mdash; carried by the music without lurching on any single hit. Then I gave it company. One hard key light, the smoke machine wound up to full, and half a dozen of these presences drifting across the floor, each on its own path. A crowd.</p>
<p>The whole scene is a few equations per pixel. It runs cold and silent &mdash; no fans, no fuss &mdash; because there was never anything to load. There&rsquo;s no dancer on disk. There&rsquo;s only code, and the dark doing most of the work.</p>
<h2>The actual lesson</h2>
<p>Here&rsquo;s what stuck with me, scrubbing off hours of iterative experimentation.</p>
<p>Every time I tried to <em>say more</em> &mdash; here is a head, here are her arms, here is precisely how she sways &mdash; the thing got worse, because I was writing cheques the animation could never cash. And every time I said <em>less</em>, it got better, because I was leaving room for the one renderer I will never out-code: the person watching. Tell someone &ldquo;this is a woman dancing&rdquo; and they will lovingly itemise everything wrong with her. Show them light and shadow shifting in smoke and they will tell <em>you</em>, unprompted, that it&rsquo;s a dance floor &mdash; because they <em>want</em> it to be. Their eye finishes the sketch I was never able to.</p>
<p>I&rsquo;ve written before about constraints as craft, but this was a sharper version of it. The skill wasn&rsquo;t in drawing the dancer. It was in drawing <em>less than a dancer</em>, and trusting the dark to do the rest.</p>
<p>The visualiser is a small thing at the quiet edge of a bigger project I&rsquo;m not ready to talk about yet &mdash; that&rsquo;s a post for another day. For now I just wanted to tell you about the afternoon I sat down to animate a human being, failed completely, and discovered that failing was the entire point.</p>
<p>Put some music on. Watch the floor fill up. The illusion is complete.</p>
]]></description>
    </item>
    <item>
      <title>The Furniture Was a Lie</title>
      <link>https://jorviksoftware.cc/notes/2026/07/06/the-furniture-was-a-lie</link>
      <guid isPermaLink="true">https://jorviksoftware.cc/notes/2026/07/06/the-furniture-was-a-lie</guid>
      <pubDate>Mon, 06 Jul 2026 18:42:05 GMT</pubDate>
      <description><![CDATA[<div class="photo-frame"><img src="https://jorviksoftware.cc/notes/2026/07/06/dormitory.jpg" alt="Black-and-white photograph of a large dormitory or hospital ward with rows of metal-framed beds lining both sides of the room, small bedside stools beside each bed, tall windows along the left wall, and a doorway and interior window at the far end.">
</div>
<p class="image-caption">Photo by <a href="https://unsplash.com/@amsterdamcityarchives">Amsterdam City Archives</a> on <a href="https://unsplash.com/photos/rows-of-beds-in-a-dormitory-room-ZlUoQmqyW00">Unsplash</a></p>

<p>I spent a happy week furnishing the next game. The colony you fight through used to be a <em>working</em> place &mdash; people lived and worked there before the swarm came &mdash; and empty rooms read as exactly what they are: empty test boxes. So I filled them. Bunks against the walls, steel lockers beside them, desks with dead terminals still flickering, oil drums in the corners, recharge panels on the walls. The rooms went from &lsquo;level geometry&rsquo; to &lsquo;somewhere&rsquo; almost overnight. I was thrilled.</p>
<p>And it was all a lie, because you could walk straight through every single piece of it.</p>
<h2>Ghosts with good lighting</h2>
<p>This is the dirty secret of set-dressing: by default it&rsquo;s a <em>picture</em> of furniture, not furniture. A drum that looks like four hundred solid pounds of steel is, to the game, nothing at all &mdash; you stroll through it like a ghost, and so do the monsters. It looks superb right up until the first time your shoulder passes through a locker, and then the spell breaks completely. Suddenly the whole world is cardboard. You can&rsquo;t un-see it.</p>
<p>So the furniture had to become solid &mdash; properly solid, for you <em>and</em> for the things hunting you, so a drum is cover you can hide behind and a bunk is something a drone has to go <em>around</em>.</p>
<h2>The bit I was nervous about</h2>
<p>Here&rsquo;s why I&rsquo;d been putting it off. As I wrote about <a href="https://jorviksoftware.cc/notes/2026/07/05/i-tore-out-the-renderer-again">the other day</a>, this game draws its entire world &mdash; walls, creatures, furniture and all &mdash; as maths on the graphics card, and that card is already working hard. The machine runs warm as it is. The obvious way to make things solid &mdash; hand all that geometry to a full physics engine &mdash; felt like exactly the wrong thing to bolt onto a renderer that was already at the edge.</p>
<p>I didn&rsquo;t want a physics engine. I wanted the cheapest possible honesty.</p>
<h2>The trick: one source of truth</h2>
<p>The thing that saved me is that the furniture&rsquo;s <em>shape already exists</em>. The renderer has to know precisely how big a bunk is in order to draw it. So rather than build a second, separate collision shape &mdash; the classic route, and the classic source of bugs &mdash; I derived a dead-simple box from <strong>the exact same description the renderer draws from</strong>, once, when the level loads.</p>
<p>After that, &lsquo;is the player touching the furniture?&rsquo; is a few lines of schoolbook arithmetic: is this circle overlapping that box? It runs on the ordinary processor, costs effectively nothing, and never goes anywhere near the graphics card. Solid furniture, zero rendering cost. The fans didn&rsquo;t even notice.</p>
<p>The part I&rsquo;m quietly proud of is the <em>one source of truth</em>. The thing you see and the thing you bump into are generated from the same description. They physically cannot disagree. There&rsquo;s a whole genre of game bug &mdash; the invisible wall, the hitbox that doesn&rsquo;t match the model, the bullet that hits thin air &mdash; that comes entirely from the picture and the collision being two separate things that have drifted apart. If they&rsquo;re the same thing, that bug can&rsquo;t exist.</p>
<h2>Choosing what&rsquo;s solid</h2>
<p>&lsquo;Make it all solid&rsquo; would be lazy, mind. Solidity is a design choice, not a default. The drums and bunks and lockers are solid because cover and obstacles make the fight better. The proximity mines you drop are deliberately <em>not</em> &mdash; you step over your own mines; the monsters have to blunder onto them, which is the entire point of a mine. So the rule isn&rsquo;t &lsquo;everything is solid&rsquo;. It&rsquo;s &lsquo;everything is solid <em>on purpose</em>, and the exceptions are exceptions <em>on purpose</em>.&rsquo;</p>
<p>Nobody is ever going to praise a game for having solid furniture. It&rsquo;s not a feature; it&rsquo;s an absence of a problem, and the best you can hope for is that no one notices. But they&rsquo;d notice instantly if it were missing &mdash; the moment they walked through a wardrobe, some quiet part of their brain would file the whole game under &lsquo;fake&rsquo;.</p>
<p>That&rsquo;s the job, really. <a href="https://jorviksoftware.cc/notes/2026/05/30/the-details-are-the-software">The details <em>are</em> the software</a>. A locker you can&rsquo;t walk through is a tiny detail. It&rsquo;s also the difference between a place and a backdrop.</p>
]]></description>
    </item>
    <item>
      <title>Every Level Is Winnable, and I Can Prove It</title>
      <link>https://jorviksoftware.cc/notes/2026/07/06/every-level-is-winnable-and-i-can-prove-it</link>
      <guid isPermaLink="true">https://jorviksoftware.cc/notes/2026/07/06/every-level-is-winnable-and-i-can-prove-it</guid>
      <pubDate>Mon, 06 Jul 2026 23:42:34 GMT</pubDate>
      <description><![CDATA[<div class="photo-frame"><img src="https://jorviksoftware.cc/notes/2026/07/06/play-2-win.jpg" alt="Neon sign: Play 2 Win"></div>
<p class="image-caption">Photo by <a href="https://unsplash.com/@joana_g">Joana Godinho</a> on <a href="https://unsplash.com/photos/red-and-white-neon-light-signage-YNAJuqlcU8A">Unsplash</a></p>

<p>The next game I&rsquo;m making builds its levels itself. Every floor is laid out fresh: rooms and corridors stitched together, doors hung, a few of them <strong>locked</strong> behind coloured keycards you have to find first, the odd hidden room for the curious, an exit you have to reach to descend. Play it twice and you&rsquo;ll never see the same place twice.</p>
<p>This is a wonderful way to make a game. It is also a wonderful way to lie awake at night, because of one question that will not leave you alone once you&rsquo;ve thought it:</p>
<p><em>What if it builds a level you can&rsquo;t finish?</em></p>
<h2>The cruelty of random</h2>
<p>Random doesn&rsquo;t care about you. Give it enough rolls and it will, eventually, hand you the red keycard locked inside the room that needs the red keycard. It will wall the exit off behind a door whose key sits on the wrong side of that same door. It will build a beautiful, atmospheric, utterly impossible maze and present it to you with a straight face. And the player &mdash; reasonably &mdash; will assume <em>you</em> are the idiot who shipped a broken game, because they have no way of knowing a dice roll did it.</p>
<p>You cannot test your way out of this by hand. There are effectively infinite levels. You will never see the broken one in testing; your players will see it on day one.</p>
<h2>Don&rsquo;t trust it &mdash; prove it</h2>
<p>So the generator doesn&rsquo;t get the final say. Every time it lays out a floor, a little solver walks the result <em>before you ever see it</em>.</p>
<p>It starts exactly where you&rsquo;ll start. It floods outward through every space it can reach. When it reaches a key, it picks it up. When it reaches a door it now has the key for, it opens it and floods on through. It keeps going &mdash; reach, collect, unlock, reach further &mdash; until one of two things happens: it touches the exit, or it runs out of anywhere new to go.</p>
<p>If it can reach the exit, the floor is real, and you get to play it. If it can&rsquo;t &mdash; if the keys and doors have tied themselves into a knot &mdash; the whole level is thrown in the bin and the generator tries again. It&rsquo;s cheap, it&rsquo;s quick, and it means <strong>every single floor you ever set foot in has been proven beatable before you arrive.</strong> You might die on it. You will never be <em>trapped</em> on it.</p>
<h2>The bugs the solver couldn&rsquo;t see</h2>
<p>Here is where it gets humbling, because &lsquo;solvable&rsquo; turned out to have layers I hadn&rsquo;t accounted for.</p>
<p>The solver checks the <em>layout</em> &mdash; the rooms, the doors, the keys. It does not, it turns out, know anything about the <strong>furniture</strong>. And so I got a run of beautiful, certified-solvable levels that were nonetheless quietly impossible, because something solid had been parked in the way <em>after</em> the solver signed off:</p>
<ul>
<li>A fuel drum &mdash; a big, solid, explodable barrel &mdash; that generated dead in the middle of a doorway, sealing the room behind it like a cork in a bottle. The solver swore the level was fine. As far as it knew, the doorway was wide open. It had no idea a barrel had moved in.</li>
<li>A length of wall pipework that routed straight across a doorway, like someone had installed a turnstile and forgotten the gate.</li>
<li>A bed that stuck out just far enough into a corridor to wedge me in place. Solvable on paper. Not solvable with shoulders.</li>
</ul>
<p>Each one taught me the same lesson from a different angle: the <em>layout</em> can be perfectly winnable while the <em>furnishing</em> quietly makes it not. So now the set-dressing gets the same suspicion the layout does. Nothing solid is allowed to block a way through, and the automated checks that run on every build don&rsquo;t just confirm the maze is solvable &mdash; they confirm nothing has wandered into a doorway and ruined it.</p>
<h2>The actual philosophy</h2>
<p>I think people hear &lsquo;procedural generation&rsquo; and picture <em>letting the computer do the work</em>. In my experience it&rsquo;s almost the opposite. The generator is a slot machine &mdash; gloriously productive, completely untrustworthy. The real work isn&rsquo;t the generating. It&rsquo;s the refusing-to-trust: the bouncer standing at the door, turning away every level that can&rsquo;t prove it&rsquo;s fair before it&rsquo;s allowed anywhere near you.</p>
<p>You&rsquo;ll never play a floor of mine I didn&rsquo;t prove you could finish. What you do with that floor is entirely your problem.</p>
]]></description>
    </item>
    <item>
      <title>I Tore Out the Renderer. Again.</title>
      <link>https://jorviksoftware.cc/notes/2026/07/05/i-tore-out-the-renderer-again</link>
      <guid isPermaLink="true">https://jorviksoftware.cc/notes/2026/07/05/i-tore-out-the-renderer-again</guid>
      <pubDate>Sun, 05 Jul 2026 23:30:27 GMT</pubDate>
      <description><![CDATA[<div class="photo-frame"><img src="https://jorviksoftware.cc/notes/2026/07/05/corridor.jpg" alt="Long, abandoned corridor in a decaying building, with cracked floors, peeling walls, tall windows on both sides, and a series of arches leading towards a dark doorway in the distance."></div>
<p class="image-caption">Photo by <a href="https://unsplash.com/@tama66">Peter Herrmann</a> on <a href="https://unsplash.com/photos/a-long-hallway-with-windows-leading-to-another-room-zRv4K6EOwog">Unsplash</a></p>

<p>In the <a href="https://jorviksoftware.cc/notes/2026/06/07/strataris-the-screensaver-that-became-a-game">post about my first game</a>, <a href="https://jorviksoftware.cc/games/strataris">Strataris</a>, I confessed to one of those decisions that cost me weeks: I tore out a renderer I was genuinely pleased with &mdash; the old <a href="https://s-macke.github.io/VoxelSpace/">Voxel Space</a> terrain trick &mdash; and rebuilt the entire world as a proper 3D triangle mesh, because I wanted to be able to bank and roll and fly properly. It was painful and it was right.</p>
<p>I&rsquo;m making a second game now. And reader, I did it again.</p>
<h2>A different shape of problem</h2>
<p>The first game was about flying <em>over</em> a world. The next one is about being <em>inside</em> one &mdash; on foot, down through a tight, occupied place, room by room, in the dark, with something in there that wants you. A flying game wants a big open landscape. A game like this wants walls, corners, corridors, the half-second before the adversary steps into the light.</p>
<p>Triangle meshes would have done the job. I&rsquo;ve got one, working, from last time. The sensible thing &mdash; the <em>grown-up</em> thing &mdash; would have been to reuse it.</p>
<p>I did not do the sensible thing. When will I learn?</p>
<p>Never, I hope &mdash; because the learning is the point. Reusing last year&rsquo;s maths teaches me nothing; I only learn by building the thing I don&rsquo;t yet know how to build. That means hours, days, weeks of studying other people&rsquo;s source code, watching tutorials, falling down Wikipedia holes &mdash; and, these days, leaning on <abbr data-title="Artificial Intelligence">AI</abbr> to sanity-check the maths and catch the mistakes I can&rsquo;t see.</p>
<h2>Maths instead of models</h2>
<p>So for this new game I built the whole world out of <a href="https://en.wikipedia.org/wiki/Signed_distance_function"><strong>signed distance fields</strong></a>, and I&rsquo;ll try to explain that without turning this into a lecture, because I promised myself a long time ago these posts wouldn&rsquo;t be lectures.</p>
<p>Here is the entire idea. Instead of describing a wall as a list of corners &mdash; a model, a mesh, a thing you&rsquo;d load from a file &mdash; you write a tiny function that can answer one question for <em>any</em> point in space: <em>how far are you from the nearest surface?</em> That&rsquo;s it. That&rsquo;s the whole description of the world &mdash; a function that measures distance.</p>
<p>To draw it, the graphics card fires a ray out through every pixel and just&hellip; walks it forward. At each step it asks &lsquo;how far is the nearest surface?&rsquo; and takes a step exactly that big, safe in the knowledge it can&rsquo;t overshoot anything. A few steps later the distance drops to nothing and the ray has found a surface. Do that for a few hundred thousand pixels, sixty times a second, and you have a world.</p>
<p>No vertices. No meshes. No model files. A corridor is an equation. The pipework bolted along the walls is an equation. The rust is an equation. And &mdash; this is the part that sold me &mdash; <strong>so are the monsters</strong>. Horrors like the ones that stalk within this new game are not sculpted models; they are a handful of soft blobby shapes blended together with a bit of maths that melts them into one another. Distance fields <em>love</em> organic, wet, bio-mechanical things. They&rsquo;re terrible at sharp machined edges and wonderful at things that look grown rather than built. For a game about something living in the walls, that&rsquo;s exactly the right kind of wonderful.</p>
<p>It also means the &lsquo;no asset files&rsquo; rule I&rsquo;m so fond of doesn&rsquo;t just survive &mdash; it gets <em>easier</em>. There&rsquo;s no model to ship because there was never a model. There&rsquo;s only code.<sup><a id="footnote-ref-1" href="#footnote-1" data-footnote-ref aria-describedby="footnote-label">1</a></sup></p>
<h2>The part where it fought back</h2>
<p>I will not pretend this was free. It was a different <em>kind</em> of hard, and in some ways a nastier one.</p>
<p>With triangles, your mistakes are at least <em>visible</em>. Last time I had buildings floating serenely in the sky and, on one memorable evening, the entire planet rendered as a single spike stretching to infinity. Funny, in hindsight. But you could <em>see</em> what was wrong.</p>
<p>Distance-field bugs are sneakier. Get a single sign backwards and the world turns itself inside out &mdash; you&rsquo;re suddenly inside the walls looking at the back of everything. Get the maths for a surface slightly wrong and it <em>shimmers</em>, flickering in and out as the ray can&rsquo;t quite decide whether it&rsquo;s hit anything. And there&rsquo;s a performance cliff waiting the whole time: every extra creature on screen is more maths per pixel per frame, and it is entirely possible to write something correct that also turns your laptop into a small, furious heater. A good chunk of my notes from this stretch are just the word &ldquo;WHY&rdquo; at increasing font sizes.</p>
<h2>Standing on shoulders of giants, again</h2>
<p>None of this is mine. I want to be clear about that, because it matters. This technique is the beating heart of the <a href="https://en.wikipedia.org/wiki/Demoscene">demoscene</a> &mdash; the people who fit entire animated worlds, with lighting and music, into programs <em>kilobytes</em> in size, as a sport. It&rsquo;s what the wizards on <a href="https://www.shadertoy.com/">Shadertoy</a> do for fun, conjuring landscapes and creatures out of a screenful of equations. I learned it from people far cleverer than me, who did extraordinary things with it long before I came along and used it to make a monster.</p>
<p>I just keep finding that the harder road teaches me something. You can&rsquo;t drop in a model, so you have to <em>understand</em> what a wall actually is. And it turns out a wall, in this game, isn&rsquo;t a picture of a wall. It&rsquo;s the answer to a question &mdash; <em>how far am I from the nearest one?</em> &mdash; asked a few hundred thousand times every frame.</p>
<p>I think that&rsquo;s rather beautiful. I also think it cost me weeks of stress and frustration. Both things are true. That&rsquo;s the deal.</p>
<section class="footnotes" data-footnotes>
<h2 id="footnote-label" class="sr-only">Footnotes</h2>
<ol>
<li id="footnote-1">
<p>We&rsquo;re not entirely asset-free in this new game &mdash; <a href="https://jorviksoftware.cc/notes/2026/07/02/the-sound-i-could-not-fake">the audio came at a price</a>. <a href="#footnote-ref-1" data-footnote-backref aria-label="Back to reference 1">↩</a></p>
</li>
</ol>
</section>
]]></description>
    </item>
    <item>
      <title>The Sound I Could Not Fake</title>
      <link>https://jorviksoftware.cc/notes/2026/07/02/the-sound-i-could-not-fake</link>
      <guid isPermaLink="true">https://jorviksoftware.cc/notes/2026/07/02/the-sound-i-could-not-fake</guid>
      <pubDate>Thu, 02 Jul 2026 09:52:17 GMT</pubDate>
      <description><![CDATA[<div class="photo-frame"><img src="https://jorviksoftware.cc/notes/2026/07/02/monster.jpg" alt="A monster (ork) brandishing a battle-axe and laser pistol"></div>
<p class="image-caption">Photo by <a href="https://unsplash.com/@polarmermaid">Anne Nygård</a> on <a href="https://unsplash.com/photos/monster-character-holding-battle-axe-toy-fDfTR3g8ckA">Unsplash</a></p>

<p>A few weeks ago <a href="https://jorviksoftware.cc/notes/2026/06/07/strataris-the-screensaver-that-became-a-game">I published a post</a> that was, in hindsight, a little smug. I told you <a href="https://jorviksoftware.cc/games/strataris">Strataris</a> shipped with <strong>no asset files</strong> &mdash; no textures, no sprites, no audio, no fonts &mdash; everything generated in code, maths all the way down, the whole thing small enough to fit on a floppy disk. I meant every word. I&rsquo;m still proud of it.</p>
<p>This is the post where I tell you I have now broken that rule.</p>
<h2>The rule</h2>
<p>Strataris was built on a constraint I set myself for no sensible reason: nothing could be loaded from a file. If I wanted a sound, I had to <em>build</em> a sound &mdash; sit down with a synthesiser I&rsquo;d written and work out, properly, how a laser or an explosion or a crackly distress call is actually made of waveforms. It was slower and harder, and that was the point. Constraints make you understand a thing instead of importing it.</p>
<p>So when I started on the next game &mdash; currently a work in progress &mdash; I assumed the rule would carry over untouched. This game &mdash; I&rsquo;ll tell you more about it another day &mdash; is a <a href="https://en.wikipedia.org/wiki/Doom_(1993_video_game)"><em>Doom</em></a>-shaped thing: on foot this time, down through an occupied subterranean structure, room by room. Dark. Claustrophobic. The kind of game where what you <em>can&rsquo;t</em> see often matters more than what you can.</p>
<p>That, it turned out, is exactly where my lovely rule fell apart.</p>
<h2>Where the rule held</h2>
<p>First, the good news, because the rule mostly <em>won</em>. The world of this game is, if anything, more extreme than Strataris. There isn&rsquo;t a single texture or 3D model anywhere in it &mdash; the walls, the floors, the pipework, the rust, and even the creatures themselves are pure maths, drawn in real time on the <abbr data-title="Graphics Processing Unit">GPU</abbr>. The &lsquo;everything in code&rsquo; rule didn&rsquo;t just survive the move to a shooter; it got <em>more</em> stubborn. Visually, I never had to compromise at all.</p>
<p>It was sound that broke me.</p>
<h2>The thing I couldn&rsquo;t fake</h2>
<p>Here&rsquo;s what I learned, sitting in the dark with headphones on: you can synthesise a <em>gun</em>. A laser, a thump, a pickup chime &mdash; those are honest little sounds, and a synthesiser does them proudly. What you cannot synthesise, at least not with the tools I have, is <strong>a living thing</strong>. The awful noise a predatory creature makes when it notices you. The gap between &ldquo;that&rsquo;s a sound effect&rdquo; and &ldquo;there is something in here with me&rdquo; is enormous, and my from-scratch creature noises landed firmly, embarrassingly, on the wrong side of it. The synthesised sounds said <em>&rdquo;video game&rdquo;</em>. I needed them to say <em>&rdquo;run&rdquo;</em>.</p>
<p>This sort of game lives and dies on atmosphere &mdash; on dread, on the half-second before you see the thing. And I&rsquo;d built myself a rule that actively prevented me from delivering the single most important ingredient. That&rsquo;s not a principle any more. That&rsquo;s a man losing an argument with himself.</p>
<p>So I relented. There are now real recorded samples in the game. Actual throats. I&rsquo;m at peace with it.</p>
<h2>How to break a rule without betraying it</h2>
<p>But I didn&rsquo;t just buy a sound library and bolt it on, because <em>that</em> would have been a betrayal &mdash; and because everything I make is free, public domain, no catch, and I wasn&rsquo;t about to ship something I couldn&rsquo;t give away clean. So two rules held over the rule I&rsquo;d broken.</p>
<p>First: every sample is <strong>Creative Commons or public domain</strong>, properly credited, free for me to ship and free for you to have. Not one borrowed byte I don&rsquo;t have the right to give away.</p>
<p>Second &mdash; and this is the part I&rsquo;m actually pleased with &mdash; the <em>code</em> still does nearly all the work. The sample is only the seed.</p>
<p>Take the worst thing in the game. Her voice is a handful of short, mammalian growls &mdash; a few seconds of audio, all told. But you never hear them as they were recorded. Every time she snarls, the game pitches the clip <strong>down</strong> &mdash; she&rsquo;s enormous, she should <em>sound</em> enormous. We also shift the pitch and speed by a random nudge each time, so no two snarls are ever quite the same. A few seconds of source becomes a creature that never repeats herself. The maths is still running; it&rsquo;s just running <em>on top of</em> a real throat instead of trying to be one itself.</p>
<p>The lesser horrors get the same trick from the other direction. When one is right behind you, you hear it clean. When one is far off down a corridor you haven&rsquo;t reached yet, you hear it <strong>muffled and drenched in echo</strong>, as though it&rsquo;s carrying through a hundred metres of steel &mdash; the same clip, run through reverb and a filter, turned into a thing you can hear but not yet find. None of that is in the sample. That&rsquo;s code, doing what code is good at.</p>
<p>And the &lsquo;soundtrack&rsquo;? There isn&rsquo;t one, not really &mdash; there&rsquo;s no <em>track</em>. There&rsquo;s a stack of looping layers (a low room-tone, a pulse, a dissonance&hellip;) and a live signal that measures how much trouble you&rsquo;re in and quietly crossfades between them. The music doesn&rsquo;t play <em>at</em> you; it <em>responds</em> to you. It thickens as the things close in and thins out when you&rsquo;re safe, and most players will never consciously notice it&rsquo;s happening. Which is exactly the point.</p>
<p>Oh &mdash; and the synthesiser I was so proud of, the one I built for Strataris? It&rsquo;s still in there. It didn&rsquo;t die. It became the <strong>fallback</strong>: if a sample is ever missing, the game quietly synthesises a stand-in so it never falls silent. The old religion got a job as the safety net. I find that quite funny.</p>
<h2>The actual lesson</h2>
<p>The mistake people make with constraints &mdash; the mistake <em>I</em> made &mdash; is letting them curdle from a tool into a dogma. The floppy-disk rule was the best creative decision on Strataris. It would have been the worst decision on this one, if I&rsquo;d held it like scripture. The craft was never in the rule. It&rsquo;s in knowing which afternoon to keep it and which afternoon to break it &mdash; and then, when you do break it, breaking it with enough discipline that the result still feels like <em>yours</em>.</p>
<p>Playability beats fidelity. I&rsquo;ll die on that hill. But atmosphere beats <em>purity</em> &mdash; and it took a monster in a dark room to teach me the difference.</p>
<p>I&rsquo;ll tell you when you can play it. For now, just know that when you finally hear her in the dark, that wasn&rsquo;t a synthesiser. That was the one time maths wasn&rsquo;t enough &mdash; and I&rsquo;m glad I stopped arguing with myself and listened.</p>
]]></description>
    </item>
    <item>
      <title>One Television, One Kilobyte</title>
      <link>https://jorviksoftware.cc/notes/2026/06/20/one-television-one-kilobyte</link>
      <guid isPermaLink="true">https://jorviksoftware.cc/notes/2026/06/20/one-television-one-kilobyte</guid>
      <pubDate>Sat, 20 Jun 2026 05:28:08 GMT</pubDate>
      <description><![CDATA[<div><img src="https://jorviksoftware.cc/notes/2026/06/20/zx81.jpg" alt="A Sinclair ZX81 — a small, flat, black wedge of a home computer with a printed membrane keyboard"></div>
<p class="image-caption">Photo by <a href="https://commons.wikimedia.org/wiki/User:Evan-Amos">Evan-Amos</a>, <a href="https://creativecommons.org/licenses/by-sa/3.0/">CC BY-SA 3.0</a>, via <a href="https://commons.wikimedia.org/wiki/File:Sinclair-ZX81.png">Wikimedia Commons</a></p>

<p>Something set me thinking about the <a href="https://en.wikipedia.org/wiki/ZX81">ZX81</a><sup><a id="footnote-ref-1" href="#footnote-1" data-footnote-ref aria-describedby="footnote-label">1</a></sup> today, and once I&rsquo;d started I couldn&rsquo;t stop. The first computer I ever touched. A black plastic wedge about the size of a paperback, one kilobyte of memory, a flat membrane keyboard with no give in it at all. I&rsquo;d have been twelve, maybe thirteen. Forty-odd years ago now, and I can still feel it under my fingers.</p>
<p>The thing I remember most vividly isn&rsquo;t even the machine. It&rsquo;s the manual.<sup><a id="footnote-ref-2" href="#footnote-2" data-footnote-ref aria-describedby="footnote-label">2</a></sup> They don&rsquo;t make them like that any more &mdash; it was <em>sublime</em>, honestly, a proper ring-bound book that taught you from nothing. I worked through it cover to cover, typing the listings in, running them, watching them do their thing. And then changing one line just to see what happened. Make the number bigger. Swap two lines round. Take that bit out &mdash; does it still work? Why not? Hours went like that. I didn&rsquo;t know it was the same process loop I&rsquo;d still be running four decades later. I just thought it was the best game in the world.</p>
<p>The magazine listings were another matter. You&rsquo;d see this gorgeous artwork next to the code &mdash; spaceships, dungeons, whole worlds &mdash; and you&rsquo;d type the whole thing in over an evening, hunt down the one character you&rsquo;d got wrong that stopped it running, finally get it going, and&hellip; a few blocky black shapes shuffling about on a white screen. Nothing like the picture. I wasn&rsquo;t even really disappointed. I was too busy being amazed that <em>those</em> lines made <em>that</em> happen at all.</p>
<div class="callout-box callout-split">
<div class="callout-main">
<strong>Fun Fact</strong>
<p><small>That 16KB, 3.25 MHz machine ran Doom. No, not really, but we did have <a href="https://en.wikipedia.org/wiki/3D_Monster_Maze">3D Monster Maze</a>, which was the Doom of its day.</small></p><br>
<p><small>Image Credit: <em>3D Monster Maze</em> (Malcolm Evans, 1981), screenshot via <a href="https://en.wikipedia.org/wiki/File:3D-monster-maze-T-rex-2-steps-away.png">Wikipedia</a></small></p>
</div>
<div class="image-center no-chrome"><img src="https://jorviksoftware.cc/notes/2026/06/20/tableau-screen-3d-monster-maze.png" alt="A black-and-white first-person view down a blocky maze corridor in 3D Monster Maze, with the Tyrannosaurus rex two steps away"></div>
</div>

<p>And I had to earn the time to do any of it. We had one television in the house &mdash; one &mdash; and the ZX81 plugged straight into it, so computing meant taking over the only screen the whole family shared. So I did housework for it. Washing-up, hoovering, mowing the lawn, washing the family car, whatever was going, traded for an hour at the keyboard. Funny the things that stick. I&rsquo;ve never since had to work quite that literally for a go on a computer, and I don&rsquo;t think I&rsquo;ve ever valued an hour at one quite as much either.</p>
<p>Then there was the tape. No disk, no anything &mdash; you saved to a cassette recorder, an ordinary one, and loaded back the same way, this banshee shriek of noise while the screen tore itself into stripes. And the waiting. What felt like hours of it. Watching, hoping, only for the thing to fall over two-thirds of the way in. The volume had to be <em>just so</em> &mdash; too quiet and it wouldn&rsquo;t catch, too loud and it garbled &mdash; and finding that sweet spot was black magic. Rewind. Nudge the dial. Try again. Hold your breath at the bit where it died last time. Sometimes the fifth attempt worked when the first four hadn&rsquo;t, and none of us ever knew why.</p>
<p>The real heartbreak, though, was the <abbr data-title="Random Access Memory">RAM</abbr> pack. One kilobyte is nothing, so like everyone I had the 16K pack that plugged into the back to give you room to actually do something. And it <em>wobbled</em>. The connection was just a friction fit on an exposed edge, and the slightest knock &mdash; a nudge of the desk, a heavy footstep, an elbow, a shadow in the wrong place &mdash; could jolt it and crash the whole machine dead. No autosave. No save at all unless you&rsquo;d done it yourself, to tape, with all the ritual that involved. So when the wobble caught you halfway through an hour of carefully typing a listing in, line by single-keyed line, it took the lot. Black screen. Start again. I learned to save early and save often the hard way, aged twelve, at the hands of a loose connector. Blu-tack was the technical solution to the problem. But it never stopped feeling tenuous and I&rsquo;ve never quite lost the little flinch I get near unsaved work. Don&rsquo;t think I&rsquo;d want to.</p>
<p>But the joy. The first time I got a program of my own working &mdash; not from the manual, not from a magazine, <em>mine</em>, dreamed up and built line by line &mdash; that was something else entirely. I couldn&rsquo;t tell you now exactly what it was; a daft little loop, I think, that filled the screen with my own name scrolling away over and over. It was nothing. And it was one of the purest hits of happiness I&rsquo;ve ever had. I&rsquo;d told a machine what to do, in a language it understood, and it had just&hellip; done it. Exactly, instantly, without a word of complaint. You only get to feel that for the first time once, and I got to feel it on a one-kilobyte wedge wired into the family telly, in between rounds of washing-up.</p>
<p>After the ZX81 came a <a href="https://en.wikipedia.org/wiki/Commodore_64">Commodore 64</a>, an <a href="https://en.wikipedia.org/wiki/Acorn_Electron">Acorn Electron</a> &mdash; &ldquo;it&rsquo;ll help with my schoolwork&rdquo;, an <a href="https://en.wikipedia.org/wiki/Atari_8-bit_computers">Atari 800XL</a> (and with it a 14-inch colour TV of my own), then the great leaps to a <a href="https://en.wikipedia.org/wiki/Atari_ST">520ST then a 1040ST</a>, an <a href="https://en.wikipedia.org/wiki/Amiga_1200">Amiga 1200</a> &mdash; that whole tribal, fiercely-argued-over era when the machine you owned said something about who you were. Then PCs, as one did. Then the original <a href="https://en.wikipedia.org/wiki/IMac_G3">Bondi-blue iMac</a>, which is where my Mac story started. A <a href="https://en.wikipedia.org/wiki/Power_Mac_G5">PowerMac G5</a> after that, and then a long line of MacBooks right up to the one I&rsquo;m typing this on, which has something like thirty-six million times the memory of that first little wedge. I&rsquo;m not exaggerating for effect. The maths just lands there.</p>
<p>Funny how it works out. A hobby &mdash; mucking about with games and listings on a borrowed telly in 1982 &mdash; and it quietly set the course of my entire working life without my ever quite deciding it should. The loop I fell into back then, the one the manual taught me &mdash; type something, run it, change a line, run it again &mdash; is the very same principle I use today. Forty years on, I&rsquo;m still writing code. And after all of it, all those machines, all those years, I&rsquo;m still faintly amazed that I can get this little electronic box to bend to my will.</p>
<section class="footnotes" data-footnotes>
<h2 id="footnote-label" class="sr-only">Footnotes</h2>
<ol>
<li id="footnote-1">
<p>Brainchild of the legendary <a href="https://en.wikipedia.org/wiki/Clive_Sinclair">Sir Clive Sinclair</a>. <a href="#footnote-ref-1" data-footnote-backref aria-label="Back to reference 1">↩</a></p>
</li>
<li id="footnote-2">
<p>The Sinclair Basic Manual (<a href="https://jorviksoftware.cc/notes/2026/06/20/zx81-basic-manual.pdf"><abbr data-title="Portable Document Format">PDF</abbr></a>) <a href="#footnote-ref-2" data-footnote-backref aria-label="Back to reference 2">↩</a></p>
</li>
</ol>
</section>
]]></description>
    </item>
  </channel>
</rss>
