Small experiments
Twelve from-scratch builds: a programming language, a database, a chess engine, a renderer, a tiny operating system and a few more.
I made these to see what I could build in one quick session, how much creativity would come out of it, and whether any of it would give me an idea for a project worth doing properly. The result is twelve small projects. Each takes something I use every day without thinking about it, like a programming language, a database, a chess opponent, or the way a computer draws a picture of a room, and builds it from nothing. No ready-made parts for the interesting bit. It is a bit like making your own pasta instead of opening a jar: slower and messier, but you find out what was in the jar.
Some of them you can look at. One is a ray tracer, which draws a picture by following rays of light backwards from the camera and seeing what they hit. It has to do that many times per pixel, and the slider shows why. With one try per pixel the picture is full of grain. With sixty-four it is much smoother, though still a little grainy.
1 try per pixel64 tries
Others move. There is a cloth that falls and swings, a billiards break, and a figure-eight orbit of three bodies that stays stable for twenty laps. A separate program turns that orbit into sound, one pitch per body. Others learn. A digit reader I wrote from nothing gets handwritten numbers right about 99 times out of 100. Another one reads a lot of Shakespeare and then writes its own, which sounds like Shakespeare if you do not listen closely, with invented words in it.
The rest are the things computers are built from: a small programming language, a compiler that turns C-like code into machine instructions, a database, a chess engine, a web server, a tiny operating system that boots and shows a command prompt, and the maths that keeps messages secret. That last one is a way to learn the pieces and is not safe to use.
Some of it failed. An AI I trained to play an Asteroids-style game never really got better. A chess AI that learns by playing itself lost every game it played against an ordinary chess engine. My Newton's cradle ended up with the balls jostling into each other instead of passing the swing along.
What's next: these were a quick exploration, so none of them is finished the way a product is. Two loose ends I know about. The language can read its own source but cannot yet run itself, and the chess learner never won a game against the engine, and mostly drew against itself.
Why I built them
I wanted to see what could get built in a single quick session, how much creativity would come out of it, and whether anything along the way would inspire a project of its own. To give it some shape I picked twelve things I use all the time, a programming language, a database, a chess engine, a renderer, a tiny operating system and a few others, and built the core of each myself instead of importing a library that already does it. They are not products. Each is a few thousand lines written to reach the interesting part and stop. They were made in April 2026 and come to about 45,000 lines of Python (blank lines and comments included), plus about 11,500 lines of C, assembly and Lune.
The rules were loose. I still used numpy, scipy, matplotlib, pygame and Pillow for arrays, plots and windows, and the compiler and OS experiments use gcc, nasm and ld to assemble and link. The two learning experiments train on public data, the MNIST digits and the Tiny Shakespeare text, which the scripts download. Neither is mine.
How they fit together
Several of them ended up depending on each other. Click a box to see what it is. On a phone, use the buttons under the diagram.
1. Lune. A small scripting language with closures, pattern matching and a pipe operator, implemented four ways: a tree-walking interpreter, a bytecode VM, a transpiler to C, and a lexer, parser and compiler written in Lune itself. 8,595 lines of Python, plus the Lune and C programs around it. Its output feeds the C compiler (6) and the WebAssembly compiler (12).
2. Rendering. A ray tracer with diffuse, metal and glass materials, a BVH, triangle meshes, Perlin-noise terrain and a Cornell box, plus a software rasterizer and a progressive real-time path tracer in pygame. 4,404 lines of Python. Stands alone.
3. Neural nets. A framework on numpy with hand-written backward passes, and on top of it a CNN, a VAE, a character-level GPT, a DQN agent and an AlphaZero attempt for chess. 6,810 lines of Python. The AlphaZero part imports the chess board from 8.
4. 2D physics. Circles, walls and distance constraints with Euler or Verlet integration. Runs the cloth, a 30-link chain, a billiards break and a Newton's cradle, and there is an Asteroids clone on top of it. 2,223 lines of Python. Stands alone.
5. N-body and sound. Gravity with a fourth-order Runge-Kutta integrator, and a from-scratch audio side: oscillators, envelopes, filters, a 16-step sequencer and Markov-chain melodies. The two meet in a piece that plays an orbit as sound. 2,348 lines of Python. Stands alone.
6. C compiler. A subset of C (int, char, pointers, arrays, loops, printf) to x86-64 assembly, with a type checker, constant folding, dead-code removal and a peephole pass, plus a JIT experiment. 3,004 lines of Python. It takes the C that Lune transpiles to, after a small post-processor strips it down.
7. Database. A B+ tree on 4 KB pages with a write-ahead log, a SQL parser and executor with indexes and joins, snapshot isolation, and a Raft implementation over localhost sockets. 6,588 lines of Python. It stores the chess app's game history in 9.
8. Chess engine. Move generation, iterative-deepening alpha-beta with a transposition table, and a UCI mode for chess programs. 2,449 lines of Python, including its own AlphaZero attempt. It is the opponent in 3 and the engine behind 9.
9. Web server. HTTP/1.1 on raw sockets, a router, a small template engine, and HTTP/2 with frames, header compression and multiplexed streams. A chess web app sits on top and uses 7 and 8. 2,448 lines of Python. The crypto code in 12 wraps its sockets.
10. MiniOS. A 512-byte bootloader in assembly and a 32-bit kernel in C: screen output, interrupts, keyboard, a shell, a memory manager, paging, a heap and a read-only FAT12 driver. About 1,700 lines of C and assembly, plus build scripts. Stands alone.
11. WebAssembly runtime. A binary parser, a stack-machine interpreter and a minimal WASI layer, enough to run small modules like Fibonacci. 2,955 lines of Python. It runs what the compiler in 12 produces.
12. Crypto and Lune to Wasm. AES-128, SHA-256, HMAC, RSA and ECDH written from the specs, with a TLS-style handshake that wraps the web server, and a compiler from Lune to WebAssembly that handles integer code only. 2,294 lines of Python.
Experiment 8's own AlphaZero script imports Experiment 3's neural-net layers, and 3's AlphaZero imports 8's chess board, so those two lean on each other. A small integer-only Lune program has gone all the way from Lune source to a native executable through the C compiler. The full language does not fit through, because that compiler only knows int, char and pointers.
Rendering
Experiment 2 is a ray tracer in pure Python. Step through its features below. After the sky come surface normals, diffuse shading, shadows, a metal ball, a glass ball, and finally triangle meshes with a teapot.
What does "samples per pixel" mean?
A path tracer fires random light paths backwards from the camera through each pixel and lets them bounce around the scene. One path is a noisy guess at the pixel's colour. Averaging many of them settles the guess, and the grain shrinks roughly with the square root of the count, so four times the samples halves the noise.
The Cornell box is the classic test scene: a lit room with a red wall and a green wall, where you can see colour bleeding onto the white surfaces. Mine is plain path tracing with no direct light sampling, so even at 256 samples per pixel it is noisy, and in pure Python it takes a while. I also wrote a software rasterizer, which is the other way to draw 3D: edge functions, a z-buffer and flat, Gouraud or Blinn-Phong shading. The teapot in the second image is its output.
Languages and compilers
Experiment 1 is Lune, a small scripting language with closures, pattern matching and a pipe operator. I wrote it four times over, each version built on the last. First a tree-walking interpreter, which is clean and slow. Then a bytecode VM, a transpiler that turns Lune into C, and finally a lexer and parser written in Lune itself. The C output is hundreds of times faster than the tree-walker on a 1,000-element sort, which is about what you would expect for leaving Python. That is for sorting. On the ray tracer the C version was no faster than plain Python, because every vector is a table and every access a hash lookup.
What is a bytecode VM, and what is a transpiler?
A tree-walking interpreter reads the program as a tree and evaluates it node by node. A bytecode VM first compiles the program into a flat list of simple instructions and runs those in a loop, which is faster. A transpiler translates the program into another language, here C, so that a C compiler does the hard part.
Writing a language in itself found bugs I had not hit any other way. Lune processes string interpolation when it lexes, so you cannot write the two characters ${ inside a string, and I use chr(36) + "{" instead. Newlines are ignored inside braces, so a list literal on the line after a call was being read as an index on that call, and the fix was an explicit return. The lexer and parser in Lune parse most of Lune (match expressions are not handled), and a third piece, a compiler written in Lune, prints bytecode as text. It does not produce executable bytes, and there is no VM written in Lune, so it is not fully self-hosted.
Experiment 6 is a compiler for a subset of C: int, char, pointers, arrays, if, while and for, with printf, write and exit. It goes through a lexer, parser, type checker, an optimizer (constant folding, dead-code removal and a peephole pass) and a code generator that writes x86-64 assembly. gcc then assembles and links it, under WSL on my machine. There is also a JIT experiment that writes machine code straight into executable memory and calls it through ctypes.
Experiment 11 is a WebAssembly interpreter, with a binary parser, a stack machine and a minimal WASI layer. Experiment 12 has a Lune to WebAssembly compiler that targets it. It compiles 32-bit integer code only: arithmetic, comparisons, if/else, while loops and functions that call each other. No strings, lists, tables or closures.
Databases, servers and an operating system
Experiment 7 is a key-value database that grew into a SQL one. The base is a B+ tree with up to 100 keys per node, stored on 4 KB pages with a 64-page cache. A write-ahead log makes it survive a crash: on startup it redoes committed transactions and undoes the rest. On top of that I put a SQL parser and executor with secondary indexes, hash and nested-loop joins and EXPLAIN, then snapshot isolation, then a three-node Raft implementation over localhost sockets.
Experiment 8 is a chess engine. It is the opponent in the AlphaZero experiments and the engine behind the web app. It does iterative-deepening alpha-beta with a transposition table and quiescence search, and its evaluation is built from material, piece-square tables and pawn structure. It can also speak UCI. Experiment 9 is the web server: HTTP/1.1 on raw sockets, a router with path parameters, a tiny template engine and HTTP/2 with frames, header compression and multiplexed streams. The chess web app on top of it keeps its game history in the database from 7.
Experiment 10 is MiniOS. A 512-byte bootloader loads a 32-bit kernel in C that handles interrupts and the keyboard, has a shell, a bitmap memory manager, paging, a first-fit heap and a read-only FAT12 driver. This is its own screen, captured after boot. One line in the middle is the kernel testing its own heap: a thousand allocations, freed in shuffled order, with nothing leaked.
Physics and sound
Experiment 4 is a 2D physics engine. It has circles, walls and distance constraints that I solve by sweeping over them a handful of times a step (4 to 25 depending on the scene), and you can pick Euler or Verlet integration. It runs the cloth in the clip at the top, a 30-link chain, a billiards break and a Newton's cradle, and I made an Asteroids clone on top of it.
Experiment 5 starts with gravity. It is an N-body simulator with a fourth-order Runge-Kutta integrator, a standard accurate way to step a simulation forward, and I tested it on the figure-eight orbit, where three equal masses chase each other around one figure-eight track. The starting values are the published figure-eight solution from 2000. Total energy should stay constant in an orbit, so any drift in it is the integrator's error, and over 20 laps that drift stays within 7 × 10-14 of the starting energy.
The same experiment has an audio side I wrote from scratch: oscillators, envelopes, filters, a 16-step sequencer and Markov-chain melodies, including a three-minute generative jazz piece. The two meet in one program that plays the figure-eight as sound. Each body gets a sine tone. Its distance from the centre sets the pitch, snapped to a pentatonic scale, so closer is higher. Its speed sets the loudness, its left-right position sets the pan, and the gravitational energy opens and closes a low-pass filter. Press play for 30 seconds of it.
Learning from scratch
Experiment 3 is a neural network framework on numpy. It has dense and convolution layers (the convolution is done as one big matrix multiply), batch norm, dropout and the Adam optimizer, with every backward pass written by hand and the convolution and batch-norm ones checked against numerical gradients. On top of it I trained three digit classifiers, a VAE, a small GPT, a DQN agent and an AlphaZero attempt.
| Model | Accuracy | Errors |
|---|---|---|
| Dense net, 784-512-256-10, dropout | 97.99% | 201 |
| CNN, two conv layers and a dense layer | 99.22% | 78 |
| Deeper CNN, batch norm and augmentation | 98.85% | 115 |
The deeper one was supposed to beat the plain CNN. It aimed for 99.3% or better and ended up lower, at 98.85%. This is the plain CNN's confusion matrix, which shows which digits it mixes up.
The VAE has a two-number latent space and trained for 40 epochs. Each cell in the grid below is what its decoder draws for one point on a 20 by 20 grid across that space, and the digits blend into each other as you move.
The GPT is a character-level transformer with 128-wide embeddings, 4 layers, 4 heads and a 128-character context, trained for 5,000 steps on the Tiny Shakespeare text. It ends at roughly 1.35 training and 1.58 validation cross-entropy, single run, and its output reads like Shakespeare with invented words:
HENRY BOLINGBROKE: Who, what I afeary small well not book the death, And be my rebel'd Romeo's Duke of Glounty breath.
What didn't work
The DQN never learned to play. I wrote a headless Asteroids game for it, where the agent sees 25 numbers: the ship's position, speed and angle, and the five nearest asteroids. Over 20,000 episodes the reward hovers around zero and never clearly climbs.
What is a DQN?
A Deep Q-Network is a reinforcement-learning method. The agent plays by trial and error, and a neural network learns to predict how much future score each action is worth in the current situation. It then picks the action with the best prediction, apart from a small share of random moves so that it keeps exploring.
AlphaZero did worse. The network lost all 6 games against my alpha-beta engine in every one of ten iterations, with no wins and no draws. Those games were capped at 80 half-moves (40 moves each side) and decided on material, the engine got 0.05 seconds a move and the network 40 search simulations a move. Each iteration was 20 self-play games at 25 simulations a move, and the whole run took about 63 minutes. The network has 560,206 parameters. A second, separate attempt with its own network also ended at 0% against the alpha-beta engine.
What is AlphaZero?
AlphaZero is a method where a neural network guesses which moves look good and how well a position is going, and a tree search uses those guesses to look ahead. The network plays itself, trains on the results, and the next version replaces the champion only if it beats it clearly. Mine used a 55% win-rate threshold.
Almost every self-play game ended in a draw, so there was very little signal about who was winning. Drag through the ten iterations and see it.
Two more failures. My chaos demo was meant to show two nearly identical starting points drifting apart. I nudged one body by 10-10, and the two runs only separate to about 3 × 10-5 by the end, because the three bodies get flung apart early on (that is the jump at about time 0.6) and then coast off in straight lines. It shows sensitivity, but not chaos.
And my Newton's cradle does not work like a cradle. The balls collide, but after the first hit they swing together and jostle into each other, with some riding up over their neighbours, instead of passing the motion down the row.
The cryptography is not safe to use. Keys, primes and padding come from Python's general-purpose random module, RSA defaults to 512 bits "for speed", and the handshake is not TLS. It is a way to learn the pieces.
Unfinished
Since this was a quick exploration, none of it is polished. Lune is still not fully self-hosted, for the reasons above. I also wrote a second version of the DQN script with Double DQN, reward clipping and best-checkpoint saving, but I never got a result out of it. The HTTP/2 tests use the third-party h2 library as the client.
- Stack
- Python (numpy, scipy, pygame, matplotlib, Pillow), some C and NASM
- Status
- Prototype
- Code
- Not public






