A cartridge that thinks

The idea arrived the way the good silly ones do, sideways and slightly embarrassing. What if the Claude Code mascot [1] lived inside a Game Boy, and what if it actually woke up. Not a sprite that loops a canned animation, but a small creature that wants things, changes its mind, and talks back in its own voice. On a 4 MHz console from 1989. Offline.

I gave myself one rule before writing a line, because a project like this dies the moment it starts making excuses. The rule is that a stock Game Boy game has to keep running bit-identical. No new CPU opcode, no moved hardware register, no altered timing. If I could not have the intelligence without touching the console, I did not want it. The console is sacred. The intelligence has to earn its place as a guest.

That constraint is not a limitation on the idea. It is the idea.

Where do you hide a brain

A Game Boy cartridge is already allowed to be strange. The console does not know what is on the other side of the cartridge bus. It puts an address on the wire and reads back whatever the cartridge decides to answer [2]. Every mapper chip ever made exploits this: bank switching, real time clocks, rumble motors, all of it is the cartridge lying politely to a CPU that cannot tell the difference [3].

So the brain goes exactly there, in the one region the cartridge owns without argument: the external RAM window at 0xA000 to 0xBFFF. I wrote a mapper that behaves like an ordinary one, save RAM and ROM banking as usual, with a single extra idea. One of the RAM banks is not RAM. When the game selects bank 0xC0, reads and writes in that window stop hitting battery-backed save memory and start hitting the coprocessor.

This detail matters more than it looks. It means saves and cognition are physically separate. You can never accidentally serialize a model’s state into the .sav file, and a game that never selects that bank never knows the coprocessor exists. The intelligence is opt-in at the hardware level.

Inside the window the layout is boring on purpose. Sixteen bytes of memory-mapped registers at the top, then a context buffer the game writes into, and an intent buffer the coprocessor writes back. Direction is strict and one way per region. The game proposes, the coprocessor answers, and neither reaches into the other’s memory.

A very small instruction set

Cognition is expensive and slow compared to everything else on the machine, so the instruction set is deliberately tiny. Not micro operations, just a handful of coarse verbs. Load a grammar. Set the current context. Ask for an intent. Narrate. The game issues at most a few of these per in-game tick, and a Tamagotchi ticks about once per second of wall clock, which is the whole reason the latency of real inference is a non-issue here. The creature is idle more than ninety nine percent of the time.

From the CPU side the loop is the most classic thing in the world. Write a command byte, then poll a status register until the busy bit clears and the ready bit comes up.

write context into the buffer
COG_CMD = SET_CONTEXT      ; poll until not busy
COG_CMD = QUERY_INTENT     ; poll until ready
read the result register

No interrupts required, no timing games, nothing that a 1989 programmer would find alien. The coprocessor runs asynchronously in the emulator and the CPU only ever observes it through reads it started itself. That is what keeps the sacred rule intact.

The part I actually care about

Here is the tension that makes the whole thing interesting rather than just cute. The simulation has to be deterministic. Save states, rewind, replay, all of it depends on the machine doing the same thing given the same inputs. A language model does not do the same thing given the same inputs. Those two facts do not want to live in the same program.

The resolution is to stop treating the model’s output as computation and start treating it as input. A button press is nondeterministic too, in the sense that the machine cannot predict it, and emulators have solved that for decades by recording it [4]. So the runtime keeps a cognitive event log keyed by the emulator’s frame counter.

frame 12043 : QUERY_INTENT  seed 0x9A31C0FF  -> token 0x0007  conf 0xC4
frame 12780 : QUERY_INTENT  seed 0x9A31C104  -> token 0x0002  conf 0x9E

During live play a query runs the real model and appends the result. During replay or rewind, a query at a logged frame returns the logged result instead of thinking again. The creature’s brain is not reproducible, but the game around it is, because the one nondeterministic thing has been pinned to a frame and written down. You can scrub back and forth through a save state and the creature does exactly what it did, for the same reason a recorded speedrun does.

Model independence, by construction

The other half of the discipline is that the cartridge must never depend on which model is behind the wire. The game does not send prompts and it does not read text as logic. It exchanges numeric tokens and short structs. When the creature decides what it wants, the answer has to be an id from a small vocabulary the cartridge shipped, never free text [5].

In Rust this is one trait, one seam, and the bus never knows what is on the far side of it.

pub trait CognitiveEngine {
    fn load_grammar(&mut self, blob: &[u8]) -> Result<(), CogError>;
    fn set_context(&mut self, ctx: &[u8]);
    fn query_intent(&mut self, seed: u32) -> Result<CogResult, CogError>;
    fn narrate(&mut self, seed: u32) -> String;
}

Behind that trait sits whatever you want. During tests it is a deterministic mock with no network, so continuous integration runs with a dependency-free brain. In the real demo it is a local model served through Ollama [6], a mid-size open weights model running entirely on the machine [7]. If the model proposes something illegal in the current game state, that is fine. The token is a proposal, and the simulation rejects it exactly the way it would reject a button press that does nothing. Out of vocabulary intents are structurally impossible, so the game logic only ever validates ids it already understands.

The creature

All of that scaffolding exists to hold up something deliberately small. A blocky little creature walks around the screen. Hunger, energy and boredom drift in the background. Every so often it stops, shows a thinking bubble while the model runs, and then asks you for something with an icon and a line of text. Food, play, sleep.

The text is where it comes alive. The line is generated live by the model, in character, from a persona the cartridge carries. Give it what it wants and it reacts in its own words. Give it the wrong thing and it refuses and complains, because the refusal is also the model talking, not a fixed string. The persona I shipped is a bratty gremlin, so when it is hungry it does not say please feed me, it yells for meat. It is never the same line twice, and none of it is scripted.

The whole stack is Rust. The emulator runs the real LR35902 core [8], the eight-bit CPU at the heart of the console [10]. The creature ROM is hand written Game Boy assembly, assembled with a real toolchain [9], poking the coprocessor through the same bus a 1989 program would use. The graphics are tile data generated from code, and the cognition runs on your own machine with no cloud call anywhere in the loop.

Why bother

There is no product here and no roadmap, and I want to keep it that way. The reason this was worth a few evenings is that it forced two things I usually get to keep apart into the same room. Old hardware wants everything to be exact, predictable, and small. Modern models are none of those things. The interesting engineering was not making a model talk, which is easy now, but drawing the line that lets an unpredictable brain sit inside a deterministic machine without either one corrupting the other.

The honest answer to what it is for is that a thirty five year old console, given a guest that can think, is a genuinely strange object to hold in your hands. That was enough.


References

[1] Claude Code, Anthropic’s agentic coding tool, and the source of the mascot given a body here

[2] Pan Docs, the community reference for Game Boy hardware, including the memory map and the cartridge bus

[3] Pan Docs: Memory Bank Controllers, how mappers extend the cartridge behind the same address window

[4] libTAS, deterministic replay by recording nondeterministic inputs frame by frame, the idea the cognitive event log borrows

[5] Ollama structured outputs, constraining a model’s output to a fixed shape instead of free text

[6] Ollama, local runtime for open weights models, no cloud call in the loop

[7] Qwen3, the open weights model family used to give the creature its voice

[8] rboy, the Rust Game Boy emulator forked to host the MBC-C mapper

[9] RGBDS, the assembler and linker used to build the creature ROM

[10] Sharp LR35902 instruction set, the opcode table for the Game Boy CPU the ROM is written against