The first thing that broke was not my code. It was my sense of what a number is.
I had picked a puzzle I'd already solved in Python years ago, in about eleven lines, with a coffee going cold next to me. Parse the input, sum some things, print the answer. Then I sat down at a Commodore 64 setup, cross-assembling with ca65 from cc65 and testing in VICE before I dared touch the real machine, and realised the answer itself didn't fit in the register. Python had been lying to me by omission for fifteen years. An integer there is a polite fiction. On a 6502 it's eight bits, and if you want anything bigger you build it yourself, byte by byte, carrying the bit across with ADC like someone passing a bucket of water down a line.
Advent of Code, for anyone who somehow missed it, is Eric Wastl's yearly puzzle calendar. One puzzle a day from December 1st, unlocking at midnight Eastern, which for me means 5 AM, a time I've only ever seen from the wrong side. Everybody treats it as a language-learning playground. Rust people use it to feel productive, Haskell people to feel superior, and I say that with affection because I'm somewhere between the two camps. The retro-computer version of the event is a smaller and stranger thing, and I'm skeptical of most of what gets written about it, which tends to be about nostalgia. Nostalgia is the least interesting reason to do this.
The interesting reason is that the puzzles were obviously designed assuming gigabytes of RAM and a CPU that does a billion things per second, and then you remove all of that. What's left is the puzzle with its disguise off.
Take the input file. My input for one of the mid-calendar days was about 41 KB. On a modern machine that's nothing, you read it with one call and forget it exists. On a C64 it's most of what BASIC gives you; the machine greets you with 38911 BASIC BYTES FREE, and that's before you've stored a single variable. I did the sensible thing and wrote in assembly, but then you have to get the input onto the machine at all. A 1541 floppy drive moves data at something like 300 to 400 bytes per second over the serial bus, so loading that file took a little under two minutes. Two minutes, to load the text. I started noticing that I read my input much more carefully when it cost me two minutes to look at it.
Which brings me to something I'll defend even though I can already hear the objections: slowness is a better teacher than speed. When a compile-run-fix cycle takes 0.3 seconds you stop thinking before you press the key. You guess. The machine tells you the answer to "does this work?" faster than you can form the question. On the real hardware, my cycle was closer to four minutes once you count writing the disk image, mounting, loading, and the little ritual of typing RUN. I wrote less code and I wrote it more slowly, and what I wrote was, honestly, better. I don't love that conclusion because it sounds like something an old man says about vinyl. But I measured it, sort of, in the number of bugs per solution, and it was 2.1 on the laptop and well under one on the C64. A tiny sample. Don't quote me.
Then there's the emulator question, which people get weirdly tribal about. Use VICE, they say, it's cycle-exact. Use real iron, say the purists. My position, which I've changed twice already: develop on the emulator, because debugging on a real C64 means staring at a blinking cursor and guessing, and verify on hardware at the end. Warp mode in VICE (the `-warp` flag, or alt-W) turns a ten-minute run into a few seconds, and that's the only reason I could iterate at all. But the final run on the real machine matters, and not for mystical reasons. Timing assumptions, memory you didn't initialise, a stray write into the zero page. The emulator forgives some of that. The real chip doesn't. I once got a perfect answer in VICE and garbage on hardware, and the difference turned out to be one uninitialised byte at $FB that happened to be zero in the emulator. Ninety minutes, for one byte.
The zero page, by the way. 256 bytes of fast memory that you use as pseudo-registers, and you will fight with the operating system's own claims on half of it. If you've never had to decide, consciously, which two bytes are going to hold your loop counter, you've never really budgeted anything.
Side note, because I keep wandering back to it. The first machine I ever opened up was a ZX Spectrum my uncle had left in a cupboard, and the smell when I took the keyboard membrane off, a sort of warm dust and flux, is a smell I can still call up on demand. I mention it only because people think the retro hobby is about the machines. It's about the hands. Anyway.
Back to the part I find most useful. Some puzzles become impossible, and the way they become impossible is instructive. In 2015 there's a puzzle where you search for an MD5 hash that starts with a given number of zeroes. Part one asks for five. Part two asks for six. On a laptop, six zeroes is a pause long enough to refill your water. On a 1 MHz 6502, a single MD5 is a serious piece of work, and even if you wrote it beautifully I'd estimate somewhere around 0.9 seconds per hash, which I got from counting cycles for the 64 rounds and not from running it, so treat it as a rough figure. Part two needs several million hashes. Do the division and you're looking at weeks. I stopped there. I did not finish that one, and I'm not embarrassed, because working out why it was infeasible taught me more about the algorithm's cost than any solution I'd have copy-pasted.
That's the second-order effect nobody mentions. Modern hardware hides cost. Every elegant one-liner you write is quietly paying a bill you never see. A hashmap lookup, a string split, a sort. On the 6502 there's no hidden bill, there's only the bill, and you receive it every clock cycle. Once you've priced things that way, it's hard to go back to writing casually. You start noticing that your daily-driver code allocates inside a loop. You can't unsee it. I wouldn't call this a virtue exactly. Sometimes it makes you insufferable in code review.
And there's a cost on the other side that the romantic accounts skip. It's a terrible way to learn algorithms. If you actually want to understand Dijkstra's algorithm, doing it on an 8-bit machine mostly teaches you how to implement a priority queue in 6502 assembly without a multiply instruction, which is a different skill and arguably a less useful one. The puzzle gets buried under the platform. I think this is why the people who do well with it are the ones who already understand the puzzle cold. They solve it in a high-level language first, quietly, and port it. Cheating? I don't think so. Porting is half the craft. Although I'll admit I'd have said the opposite two years ago, that you should solve everything natively or not at all. Hm. I still sort of believe that. I just can't defend it.
What about the community side? It's tiny and it's lovely, the sort of place where somebody posts a solution for the Game Boy and someone else replies with a cycle count and a correction. Almost nobody is trying to monetise it, which in my line of work, where everything gets turned into a growth channel sooner or later, feels almost suspicious. I keep waiting for the grift to arrive. So far it hasn't. Give it a couple of years.
One more thing about the 2 minutes of loading. At some point I stopped resenting it. I'd start the load, go and put the kettle on, and come back to a program that was ready and a head that had already moved on to the next problem. There's a version of productivity that is just removing the waiting, and I've spent most of my career in it, and it's not obviously correct. Some of my best decisions in that month happened in the gaps the hardware forced on me. I wouldn't build a workflow around that, and I'd be lying if I said I'd keep the floppy drive for real work. But the gaps were doing something.
So should you do it? If you're already comfortable in assembly, yes, pick any machine you own. If you're not, begin with a cheap target like the Spectrum or a DOS box with Turbo Pascal, which still teaches the lesson without the pain of the 6502's missing instructions. And choose an early day. A puzzle from the first week, input small, answer modest. Resist the urge to prove something. I tried to prove something on day 4 and, as you now know, the arithmetic proved something to me instead.
I still have the disk. It's labelled in pen, and the label is wrong.