About — betlang☾<br>get betlang<br>menu
get betlangv0.1.0 · public beta☾
why I built bet56,000 lines of DOOM, in a language I made up.<br>bet is a small, real programming language: slang keywords on the surface, a serious compiler underneath. It compiles to native code through LLVM, manages its own memory, self-hosts, and runs DOOM. I built it as a contained experiment, and ran it to completion.
I built bet on a break from trying to make something that sells. I wanted a<br>project with a clear edge to it, one I could actually finish, and building a<br>compiler had been on my list for a long time. A joke language gave me the excuse:<br>keywords that read like a group chat, wrapped around a compiler that had to<br>genuinely work. I set one rule at the start: no research. I never checked whether<br>anyone had built this before, because I didn’t want the internet to kill the idea<br>before I’d written a line. As it turned out, Geoffrey Huntley<br>already had, and I didn’t learn that until later.
A language that only works at the level of syntax is really just a costume, so I<br>gave it one real job. Games allocate and release objects constantly, and both<br>manual bookkeeping and garbage collection cost you frames you can’t spare. bet<br>handles that with arenas: allocate into a scope, use it for a frame, drop the<br>whole scope at once. The pattern itself isn’t new. Zig ships an arena allocator<br>that frees everything in one call, and Odin bakes the same arena and<br>temp-allocator idea in for game code. I wanted bet to carry it as a first-class<br>feature with its own keywords, so the language would read as a design of its own<br>rather than a slang reskin of something that already existed.
It’s a joke. I still wanted it to actually do something.
The other reason I built it was to run an experiment. I wanted to see how far an<br>AI coding agent could get on a hard, well-specified project if I stayed out of<br>the implementation. My job was to be the architect: I set the problem and the<br>acceptance criteria, then held the line on them. I did zero code review. A change<br>shipped only if it passed its tests, met the acceptance criteria, and cleared the<br>corpus. That was the only gate.
The proof that it works is DOOM, ported in full. id Software’s original C source<br>for the renderer, the game logic, the WAD loading, and the audio was rewritten in<br>bet: more than 56,000 lines, compiled to a native binary that runs the real<br>shareware game in a window.
Launch it and DOOM plays itself in attract mode. Those clips are the game’s<br>original recorded inputs, replayed through the simulation. Fixed-point math is<br>what makes that a real test: drift by a single step and the player starts walking<br>into walls. bet replays the inputs in lockstep with the reference C build, frame<br>for frame, so the whole simulation comes out bit-for-bit identical. Getting that<br>far with no shortcuts is what separates a toy language from one that can carry<br>real software, and it’s the result I trust most on a build I never code-reviewed.
So it finished, end to end: the compiler, self-hosting, documentation, and this<br>site. And that is where it stops. If a feature request comes in I’ll review it,<br>but there’s no roadmap and no version two on the way. bet was a joke and an<br>experiment and a way to learn what a build like this<br>actually takes, and it did all of that. If there’s one thing I want it to say<br>about me, it’s that I finish what I start.
the part I took seriouslyThe memory model is the real work.<br>Manual free() bookkeeping and GC pauses both wreck a frame budget. In bet you allocate into a crib, an arena scoped to the frame, and release the whole thing with evict in O(1). No per-object tracking, no stall in the middle of a frame.<br>frame.bet<br>crib wave // arena for this frame<br>squad e in spawn(wave) { e.tick() }<br>evict wave // whole thing gone, O(1)
That is the experiment. A joke on the surface, one hard problem solved properly underneath, and a build gated by nothing but its tests. It compiles to native code, it self-hosts, and it runs DOOM. It was never meant to become a product, and it doesn't need to be one. It's finished.
how it was builtWhere the time actually went.<br>Real numbers from a commit-tracked timelog, counting active effort with idle time clamped out. That clamping has one gap: three of the agents porting DOOM stalled on usage limits while the clock kept running, so the porting-games row runs about 12 hours high. Take those off and the real total is nearer 22.<br>porting games<br>doom, oregon trail, pong
18h
self-hosting the compiler<br>bet, compiling bet
4h
security review<br>cwage issues #30–#48
3h
infra, tooling & site<br>ci, docker, freeze, docs
3h
frontend<br>lexer · parser · grammar
2h
language & collections<br>keywords, types, containers
2h
LLVM backend<br>the actual hard part
1h
arena memory<br>crib · evict · the point
29m
total≈ 34h tracked· most of it on porting games
prior art · credit where it's due<br>I didn't know cursed existed until bet was already built. Geoffrey Huntley reached the same...