How TransistorKit's NES harness works: an emulator as a truth machine
TransistorKit builds real NES cartridges from a description. That sentence hides a problem: an AI can write 6502 assembly all day, but the NES doesn’t grade on effort. A sprite lands in the wrong OAM slot, a palette bit flips, a sample sits at an unaligned address — and the game is simply wrong, in a way no compiler warning will ever tell you. Vibe-coding assembly and eyeballing an emulator window doesn’t scale.
So we stopped trusting the builder’s word and gave TransistorKit a truth machine: a full NES emulator, embedded in the app, that every build has to convince before it’s allowed to call itself done.
A headless NES inside the Mac app
At the bottom of the harness is a real emulator core — a Rust NES emulator (built on tetanes-core) wrapped in a thin layer we control and compiled straight into the TransistorKit Mac app. No emulator window, no external process, no FCEUX-and-a-human in the loop. The core runs headless: load a ROM, step it frame by frame, and read out anything about the machine — CPU registers and status flags, OAM sprite slots, nametables, palette RAM, arbitrary memory.
Two properties make it an oracle rather than just an emulator:
- It’s deterministic. Same ROM, same input script, same frame count → the same machine state, every time. A verification that passes today passes tomorrow, and a failure is reproducible down to the byte.
- It’s introspectable. The whole machine is readable from the outside. We don’t screenshot a window and squint — we ask the PPU directly what color sprite 4 is.
One boundary we hold deliberately: the harness verifies and renders — it never generates. Art and code come from the builders and the art pipeline. The emulator is the referee, not a player.
The assertion runner: unit tests for 6502
The centerpiece is the in-build assertion runner. While a builder is writing the game, it can compile a routine into a minimal ROM, run it for N frames headless — with a scripted controller input per frame if the test needs one — and then assert on exact machine state:
- “The player sprite is in OAM slot 0 at y=112.”
- “After 60 frames, the carry flag is clear and $0300 holds the score digits.”
- “This nametable byte is the door tile, and its attribute bits select palette 2.”
Each test is a small manifest — ROM, input script, run-to frame, a list of assertions — and a failing assertion surfaces an expected-vs-actual diff, exactly like a failing unit test in any modern language. The difference is what’s under test: not a mock, the actual emulated machine.
This flips NES development on its head. The hardest part of writing for a 1985 console is precisely the low-level correctness you can’t see — and now it’s the most testable part of the project. Builders run these checks while iterating, not after, and a failing assertion blocks the build from going green just like a failing test suite should.
The CHR inspector: an art oracle
Code isn’t the only thing that goes subtly wrong on the NES. Graphics live in CHR banks as 8×8 tiles, colored through palette and attribute tables, and the classic failure is invisible in the source: art that converted fine but landed as the wrong tile, a duplicated tile silently eating your tiny tile budget, a sprite drawn in the wrong palette.
The CHR inspector points the same introspection at art. It loads a CHR bank, renders every tile — and full pattern tables and nametables — to images, then diffs them against the intended source art. It flags duplicate and wasted tiles, catches tile-budget overruns, and verifies palette and attribute assignments programmatically. “The sprite is the wrong shade of green” stops being something a human notices on the fourth playthrough and becomes a failed check with a picture attached.
Closing the loop
The third piece connects the two ends of the pipeline. TransistorKit’s art tools generate sprites and backgrounds; those get converted into CHR data; then the harness renders the CHR back out and diffs it against the original art. If what the cartridge will actually display doesn’t match what the artist (human or AI) intended, the build hears about it before you do.
Generate → convert → render back → compare. The generation side stays creative; the emulator closes the loop with a yes or a no.
Even audio goes through it: sampled sound (DMC) has brutal alignment rules on the NES, so the harness asserts that sample bytes sit at their aligned addresses and that the chip actually fetches them during a scripted playback run.
Why this matters
Retro targets are the hardest thing our fleet builds — low-level assembly under savage hardware constraints, where the feedback loop has historically been “run it and look.” The NES harness replaces look with prove. Every cartridge TransistorKit ships has been executed, inspected, and diffed against intent on a deterministic replica of the real machine — hundreds of times, during the build, automatically.
The builder writes the game. The machine decides if it’s true.