Skip to content
HN On Hacker News ↗

Running a Minecraft Server and more on a 1960s UNIVAC Computer

▲ 243 points 38 comments by brilee 1mo ago HN discussion ↗

Pangram verdict · v3.3

We believe that this document is fully human-written

1 %

AI likelihood · overall

Human
100% human-written 0% AI-generated
SEGMENTS · HUMAN 5 of 5
SEGMENTS · AI 0 of 5
WORD COUNT 1,725
PEAK AI % 1% · §5
Analyzed
Apr 21
backend: pangram/v3.3
Segments scanned
5 windows
avg 345 words each
Distribution
100 / 0%
human / AI fraction
Verdict
Human
Pangram v3.3

Article text · 1,725 words · 5 segments analyzed

Human AI-generated
§1 Human · 0%

Check it out! Here I am running a Minecraft server on a 1960s UNIVAC 1219B computer:Here’s a NES emulator rendering the first frame of Pinball:… and a selfie printed using the “overstrike” technique:We ran a ton more crazy stuff, including: OCaml programs (!) A webserver Curve25519 + AES encryption A BASIC interpreter ELIZA Games like Oregon Trail, Wordle, and Battleship … and so much more! All this on a 250khz computer with only 90kb RAM from the 1960s. I live for this kind of stuff! I’m obsessed with running code in weird places and smashing technical limitations. This project is my most ambitious project so far, taking about 8 months of work from myself and others.The source for the project is here. Also see TheScienceElf’s video on this project!The UNIVAC is a weird machineThe UNIVAC 1219B is a super weird machine and is hostile to modern programming in almost every way: 18 bit words. Memory addresses and values are 18 bits! Not even a power of two. Ones’ complement arithmetic, kinda. Modern computers use two’s complement to represent signed integers. This computer uses ones’ complement, but with annoying differences around signed zero that we had to reverse engineer. Just a few registers. One 36 bit register A can be individually addressed by AU:AL. You get that and another 18 bit B register. Only 40,960 words of memory. That’s only 90kb total memory to split between our code and the memory it needs at runtime. Banked memory. These 40,960 words of memory are split into 10 banks. You have to configure which bank your instructions address in advance. The computer’s original purpose was to be used by the Navy to read in radar signals and direct artillery. It really is an amazing feat of engineering. The computer is shown on the left in the image below. To its right is the (currently semi-functional) magnetic tape unit.Nearby is the teletype, which is how we interface with the computer. You can type to the UNIVAC and it can type back; everything is printed to the same sheet of paper.

§2 Human · 0%

It’s the stdin and stdout.Only two UNIVAC 1219s exist today, both rescued from Johns Hopkins University by folks from the Vintage Computer Federation. This is the only one that is operational.Before we started this project, all the programs that existed were hand-written in UNIVAC assembly. We’re going to change that by getting C compiling!The first encounter at VCF East 2025The first time I came across the computer was during a trip to VCF East in April 2025. Bill and Steven were running demo programs on the machine. Duane, Bill, and Steven had done a ton of amazing work to rescue and restore this computer over the last 10 years.Seeing this thing in person was genuinely inspiring: the flashing lights, the tacking of the teletype, the smell of the oil… I knew then that I needed to get some crazy code running on this thing. Something much more than fizzbuzz. I wanted a NES emulator. I wanted OCaml. How far could we push this hardware?We need an emulator and assemblerThe first things we need are an assembler for the UNIVAC assembly language and an emulator to run that assembled program. Luckily for us, Duane had written an assembler for UNIVAC assembly in BASIC (!) and an emulator in VB.NET many years ago.Soon after VCF was over, TheScienceElf took a stab at writing a new assembler and emulator in Rust by consulting the scans of the incredible manuals and using Duane’s implementations as a reference.The Rust emu was fast. It was 400x faster than the real UNIVAC hardware and 40,000x faster than the VB.NET emulator. This speed turned out to be entirely necessary to power the fuzz testing I’ll discuss later.Both emulators weren’t hardware accurate at this point, but it was good enough to start!Wee as a first attempt at a C compilerNow that we have an emulator, how can we get C code running in it?The fastest way to prove out a C compiler was to use wee, an old project of mine. It’s a tiny instruction set I’ve used previously to compile C to weird places.It worked, but holy moly it was bad.

§3 Human · 0%

A trivial fizzbuzz program took up ~27k words, or about 67% of the total memory of the computer. It took a full minute to compute the first 100 fizzbuzz lines. Since my goal was to get real and complex programs running, this was clearly not viable.A RISC-V emulator is the moveWe have to do something smarter than wee. There are many options, so let me clarify my main two goals: I want to run real, big, interesting programs. I want to compile straight from github and let it rip on the machine. It’s less important that these real programs run maximally fast. I must maintain my sanity. We need to use a real compiler, like LLVM or GCCI need all of the following to accomplish the goal of running real programs: Full C standard library. In this case I used picolibc. Soft float and other legalizations. I need all the types and operations to work. Floats, doubles, int32, int64, everything. Even though the UNIVAC doesn’t have hardware to do this natively. Dead code elimination + size optimization. We need to pack things tightly into 90kb of space. Other languages. I want to support more than C, like Rust, C++, Zig, etc. Directly compiling to the UNIVAC won’t cut itWriting an LLVM or GCC backend for the UNIVAC would be absolutely nightmarish and would violate my second goal to maintain my sanity. The ones’ complement arithmetic, 18-bit words, and banked memory would all be painful to hack into modern compilers.And even if we did, to actually benefit from direct compilation, your C ints would be 18-bit ones’ complement ints. That’s technically allowed by the C spec (at least until C23 mandated two’s complement), but in practice, real code often assumes >=32-bit two’s complement, so off-the-shelf programs would break.So emulate a target GCC already supports, like RISC-VThe idea is to use GCC to compile C to RISC-V, and then emulate that RISC-V on the UNIVAC by writing a RISC-V emulator in UNIVAC assembly.Think about how nice this is: One and done. Write the emulator once and never look at UNIVAC assembly again.

§4 Human · 0%

You can fuzz it. You can have high confidence that the emulator is correct by generating random RISC-V programs, running them through the emulator and a reference emulator, and comparing the final state of the registers. Incremental dopamine. I read a blog post many years ago that stuck with me about structuring projects in a way that gives incremental dopamine throughout the implementation. If you try to write the whole project and only test things at the end, you may burn out before you’re positively rewarded by seeing something work. The base RISC-V instruction set has only 38 instructions we care about, which means there’s a clear end goal. We can check them off as we implement them and they pass the fuzz tests. Dense binaries. We can encode a RISC-V instruction efficiently into 2 18-bit UNIVAC words to efficiently pack them into our limited memory. This also reserves us the option in the future to implement the compressed extension or add additional bespoke compression methods. Emulation is slower, but that’s fineThe real downside of this approach is the runtime penalty to decode and emulate each instruction. After all the optimizations, it takes ~40 UNIVAC instructions to emulate 1 RISC-V instruction. That means that our 250khz UNIVAC computer can run a ~6khz RISC-V computer.… and that’s pretty good! The real obstacle to running real, complex programs is that 40kw of memory. This emulation gives us the best space efficiency along with its other benefits.Building the toolchainHere’s the high level flow of the toolchain: Write C. Compile to RISC-V with GCC. Re-encode each instruction into a UNIVAC-efficient format, 2 words per RISC-V instruction. Append these re-encoded instructions to the emulator’s source. Assemble the program into a .76 tape file to be loaded onto the machine. Writing ~1000 lines of UNIVAC assembly for the RISC-V emulator isn’t going to be easy; you have to have good tooling before doing this. Before I ever started writing this program, I spent a couple weeks preparing: An emacs major mode. OCaml tooling for parsing, emulating, and re-encoding RISC-V, with round-trip fuzzing.

§5 Human · 1%

Differential fuzzer that checked my UNIVAC RISC-V emulator against a ground truth (mini-rv32ima). Efficient test case reducer (using a port of Lithium). And oh boy this investment paid dividends.Claude Code can’t write UNIVAC assembly yetClaude Code is great – it wrote the entire emacs major mode for me given the instruction docs. I use it frequently for code editing tasks as I write OCaml. To my dismay though, even with the docs, emulator, and differential fuzzer, Claude Code fell on its face when writing UNIVAC assembly. I can’t really blame it. UNIVAC assembly is just really weird.No matter what I did, at this point of the project, Claude Code could not internalize the UNIVAC’s idiosyncrasies, like its ones’ complement arithmetic, the fact that left shift is circular and right shift is arithmetic, and the weird instruction special cases, like CPAL behaving differently with 0.I can write UNIVAC assembly, thoughThere are moments in all programmers’ lives where you have to just lock in and grind it out. So I rolled up my sleeves, and in a matter of a few days, I typed the ~1000 lines of UNIVAC assembly to implement the 38 RISC-V instructions we needed from the base set. It was honestly an enjoyable experience!The emacs major mode enables syntax highlighting and provides help text that shows the timing of the instruction.The fuzz testing caught bugs and reduced them to a minimal repro instantly. Once the fuzzer passed for an instruction, I happily moved on; I didn’t care about efficiency at this point, just correctness.The first C program works!Once all the fuzz tests were passing, I ran my first C program. It…. almost worked! There was a small bug in how RISC-V memory addresses translated to UNIVAC memory addresses. I updated my fuzzer so that it would catch the bug, fixed it, and all the C programs just worked from that point on! I thanked my past self profusely for writing the fuzzer.This was an amazing moment. Fizzbuzz worked.