Open-source project
mame/optcarrot avatar
mame/optcarrot

optcarrot: a NES emulator built to measure Ruby rather than to play games

A NES emulator written in Ruby

867 stars53 forksRubyMIT

At a glance

What is it?
optcarrot is a NES emulator written in Ruby that exists as a benchmark for Ruby implementations, targeting 20 fps in Ruby 2.0 so a Ruby3x3 speedup would make it playable. The author says plainly that it is not the emulator to use for gaming.
Who is it for?
optcarrot suits Ruby implementers who want a repeatable workload to compare interpreter speed, and people who want to read a small NES emulator written in clear Ruby. It does not suit anyone expecting a playable or general purpose emulator, since the project recommends another emulator for that and ships only sample ROMs.
Can I use it commercially?
Yes. MIT is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
Is it still maintained?
Yes. The repository last received commits 170 days ago.
What is it written in?
Mainly Ruby, according to GitHub's language statistics.

Answers come from the project's GitHub data, last synced on October 25, 2026, and from our analysis. They are not legal advice.

Editorial analysis

The goal is a workload that exposes Ruby interpreter speed

optcarrot is a NES emulator written in Ruby, but the README is quick to set expectations about what it is for. The stated project goal is to provide an enjoyable benchmark for Ruby implementations, and the specific target is a NES emulator that works at 20 fps in Ruby 2.0. An original NES runs at 60 fps, so the framing is a ratio between the hardware and the interpreter, not between two emulators.

The reasoning behind that number is the Ruby3x3 idea, the claim that Ruby 3 would be three times faster. If that speedup arrives, the emulator becomes something you can actually enjoy playing. So the audience is a specific one: people working on Ruby implementations, benchmarking Ruby, or curious about how a real-time workload behaves under a given interpreter.

The author is equally direct about the limits. The README states that the project does not aim to create a practical NES emulator, notes that many good emulators already exist, and recommends using another emulator if you just want to play a game. That framing matters when you read the repository: this is a measurement instrument that happens to be a complete program.

Twenty frames a second is the number the whole design answers to

Everything about the project orbits one constraint. A NES runs at 60 fps, so 20 fps gives you a third of real time, and the 20 fps figure was set for Ruby 2.0, the interpreter generation the project was written against. Emulating the console means emulating the CPU, the picture processing unit and the sound hardware in sequence, which is why the workload has enough branching and memory traffic to be worth measuring.

The benchmark reports are framed in the game's own clock rather than in wall time. The README shows FPS after 3 seconds in the game's clock, then a second chart of FPS after 50 seconds in the game's clock, with the note that only fast implementations are listed in the longer run. That distinction matters for reading the numbers. A short run lets a slow interpreter hide behind start-up costs and warm-up behaviour, and the shorter chart includes implementations that drop out of the longer one. The README also links to continuous benchmark results for Ruby releases and for Ruby commits on rubybench.org, where the result type is given as Optcarrot Lan_Master.nes.

One warm-up caveat is worth repeating because it comes from outside the repository. The README points to a post by eregon about the performance potential of TruffleRuby after warm-up, which is a reminder that the numbers for a just-in-time compiler can look very different once it has settled.

Running it needs SDL2, a clone and a ROM path

The basic usage section is short because the program takes one ROM file as its argument. SDL2 is required, and the README gives three commands:

bash
    $ git clone http://github.com/mame/optcarrot.git
    $ cd optcarrot
    $ bin/optcarrot examples/Lan_Master.nes

The third line passes a ROM file from the examples directory. The repository ships several: Lan_Master.nes, plus zip archives named DABG.zip, Lan Master.zip, alter_ego.zip, lawn_mower.zip, thwaite-0-03.zip and zooming_secretary1-02.zip, and a source.yml that points at where they came from.

Input is handled with the keyboard. The README's key table maps the arrow keys to the D-pad, Z to the A button, X to the B button, space to Start and return to Select. There is no configuration file involved in that mapping, and the README does not describe one.

Advanced usage lives in a separate document, doc/bonus.md, which the README links for anything beyond the three commands above.

The --opt flag rewrites Ruby at runtime to remove bottlenecks

There is a second documented way to run the emulator, and it changes what the program does internally:

bash
    $ bin/optcarrot --opt examples/Lan_Master.nes

The README states that this option may run faster, and describes the mechanism bluntly: it generates an optimized and, in the project's words, super-dirty Ruby code internally, replacing some bottleneck methods with the generated versions. The details are in doc/internal.md.

This is the part of optcarrot that is most interesting to a Ruby implementer and least useful to a gamer. Instead of relying on the interpreter to optimise a hot loop, the program rewrites it. It also explains why the benchmark numbers need their measurement conditions spelled out, since the program can be running in two quite different modes at once, with and without the generated code. doc/benchmark.md is where the README says to look for the measurement conditions behind the charts.

The project also ships development scaffolding in the repository root: a Gemfile, a Rakefile, optcarrot.gemspec, benchmark.yml, .travis.yml and .rubocop.yml. That mix of a continuous integration file, a rubocop configuration and a benchmark definition file is consistent with a repository maintained as a measurement instrument rather than as an application.

The repository is split into a library, a driver and the ROMs

Top level entries are bin/, lib/, doc/, examples/ and tools/, next to the Gemfile, Rakefile and gemspec. The split tells you how the program is put together: the emulator logic lives under lib/, the command line entry point lives under bin/ as the optcarrot executable the usage instructions call, and the ROMs are data under examples/ rather than embedded in the source.

The tools/ directory is separate again, and its presence suggests the repository includes helpers beyond the emulator itself, consistent with a project whose output is benchmark data as much as a running game.

The documentation set is small and purposeful. There are five markdown documents named in the README: doc/bonus.md for advanced usage, doc/benchmark.md for measurement conditions and further charts, doc/internal.md for the generated-code mechanism, plus the two benchmark chart images under doc/ named benchmark-summary.png and benchmark-summary-3000.png, the latter naming the 3000 frame run. The slide deck linked from the project is a Tokyo RubyKaigi 11 talk, which dates the public presentation of the work.

The acknowledgement section is unusually informative about how the project was built. It credits the NESdev Wiki and the NESdev NES Reference Guide as the basis for the hardware analysis, states that the source code of Nestopia and NESICIDE was read, and says the test ROMs used come from NESICIDE.

MIT licence, no tagged releases, and a last push on 2026-05-11

The licence is MIT, which is the most permissive of the common open source licences and imposes no obligations on adopters beyond keeping the copyright and licence text with redistributed copies. That is a short paragraph in the LICENSE file at the root of the repository, and it is worth noting that the emulator code is separate from the ROM data, since the example ROMs carry their own provenance in examples/source.yml and were obtained through NESICIDE.

Maintenance timing is the part that needs attention. The last push was on 2026-05-11, and the repository is not archived. There are no GitHub releases, so there is no tag list or release note history to read there; the project is delivered as a repository and a gem, with optcarrot.gemspec and Gemfile at the root, and the README points to the Ruby benchmark services for the running results rather than to release artefacts.

The project also documents its own communication channels. A slide deck from Tokyo RubyKaigi 11 is linked under See also, and the Ruby3x3 video the whole project is built around is the reference for the speedup target that decides whether a 20 fps emulator becomes a playable one.

Where optcarrot is the wrong tool

The clearest case for not using it is stated in the README itself: if you want to play a game, use another emulator. This one is a benchmark first, and the author does not pretend otherwise.

The second case is a missing dependency that is easy to trip over. SDL2 is required, so on a machine without it the program will not start, and the README gives no build instructions for installing it. On top of that, the repository ships sample ROMs rather than a general ROM set, so anything outside those examples has to come from elsewhere, and the ROM you benchmark with changes the workload.

The third case is interpreting the numbers. The FPS figures in the project are tied to a specific ROM, Lan_Master.nes, and to a clock measured in game frames rather than wall time, with the 3 second and 50 second charts measuring different things. The generated-code --opt path also produces a different program from the default path, so a measurement made in one mode does not describe the other. Anyone comparing implementations needs to hold ROM, mode and run length fixed before drawing a conclusion.

Finally, treat the benchmark as a workload, not a benchmark harness. The repository gives you a program and a set of charts, and links out to rubybench.org for continuous results. It does not present itself as a general benchmark suite for comparing arbitrary Ruby programs.

Editorial conclusion

optcarrot suits Ruby implementers who want a repeatable workload to compare interpreter speed, and people who want to read a small NES emulator written in clear Ruby. It does not suit anyone expecting a playable or general purpose emulator, since the project recommends another emulator for that and ships only sample ROMs. Before adopting it, check that SDL2 is present in your environment and that bin/optcarrot examples/Lan_Master.nes runs, since every published FPS figure in the project is tied to that ROM and that clock.

Frequently asked questions

What is optcarrot and what is it used for?

It is a NES emulator written in Ruby that the project positions as a benchmark for Ruby implementations, with a target of 20 fps in Ruby 2.0 against the 60 fps of an original NES. The README says it is not intended as a practical emulator and recommends another emulator for playing games.

How do I run optcarrot?

SDL2 is required. Clone the repository, change into it, and pass a ROM path to the executable, for example bin/optcarrot examples/Lan_Master.nes. Advanced options are described in doc/bonus.md.

What does the --opt option do in optcarrot?

It generates optimized Ruby code internally and replaces some bottleneck methods with it, which the README says may make the emulator run faster. The mechanism is documented in doc/internal.md.

How are the optcarrot benchmark numbers measured?

The README shows FPS after 3 seconds in the game's clock, and a second chart after 50 seconds in the game's clock where only fast implementations are listed. The measurement conditions are described in doc/benchmark.md, and continuous results are linked from rubybench.org.

Official sources

  1. Issues
  2. License: MIT
  3. mame/optcarrot on GitHub
  4. README
Add this badge to your README

If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.

Add this badge to your README

markdown
[![Hysen Labs](https://hysenlabs.com/badge/mame-optcarrot.svg)](https://hysenlabs.com/projects/mame-optcarrot)

Was this useful?