SkillAgentSearch skills...

Ruby2600

An experimental Atari™ 2600 emulator, 100% written in Ruby

Install / Use

/learn @chesterbr/Ruby2600
About this skill

Quality Score

0/100

Supported Platforms

Universal

README

ruby2600

An experimental Atari™ 2600 emulator, 100% written in Ruby.

ruby2600

These RubyConfBr 2013 slides showcase the general architecture and rationale.

Current status

Build Status

Games working with no noticeable glitches include:

  • Pitfall!
  • Space Invaders
  • River Raid
  • Pac-Man
  • Tennis
  • Donkey Kong

You can load most 2K and 4K carts and try them out.

Speed is very low: about ~2 FPS (from the expected 60) on a 2.3Ghz computer. An accelerated video shows how the emulator would run in full speed.

Also, no sound is emulated, nor any controllers other than the console switches and player 0 joystick.

Check the Known Issues and FAQ below for more information.

screenshot

Installation

Standard Ruby (MRI) will use Gosu to display the frames and capture user input, so you need to ensure its dependencies are installed. On OS X, this does the trick:

brew install sdl2 libogg libvorbis

(you don't need that if you run on JRuby - which uses the built-in SWT - or if you will run headless)

git clone git@github.com:chesterbr/ruby2600.git
cd ruby2600
# cp .ruby-version-for-jruby .ruby-version # Only for JRuby
rvm install # (or rbenv install, or whatever you need to install the Ruby in .ruby-version)
gem install bundler
bundle

Usage

To run on MRI:

bundle exec ruby -Ilib bin/ruby2600 /path/of/your/romfile.bin

If you're using JRuby:

bundle exec jruby -J-XstartOnFirstThread -Ilib bin/ruby2600-swt /path/of/your/romfile.bin

For profiling/performance testing, you'll likely prefer headless mode, e.g.:

bundle exec ruby-prof bin/ruby2600-headless /path/of/your/romfile.bin 1

(the number in the end is the number of frames to run; an extra one will be added, since first frame of most games is kind of bogus)

Notice you can add support to other VMs by creating a run script under [/bin][https://github.com/chesterbr/ruby2600/tree/master/bin].

Keys

  • ↑ ← ↓ → - Player 0 joystick
  • Space - Player 0 fire button
  • 1 - GAME SELECT switch
  • 2 - GAME RESET switch
  • 3/4 - Color switch (3 = Color; 4 = black and white)
  • 5/6 - Player 0 difficulty switch (5 = Beginner, 6 = Advanced)
  • 7/8 - Player 1 difficulty switch (7 = Beginner, 8 = Advanced)
  • W/A/S/D - "Sticky" Player 0 joystick (to stop moving, press the non-sticky matching arrow)

Technical details

  • Full 650x CPU instruction set emulation and test, cloc-ing less than 380 lines of code. (hardware interrupts not emulated, since the 2600 does not have those);
  • RIOT fully implemented and tested;
  • All bus mirrorings implemented and tested; console switches and P0 joystick bindings available for any pluggable front-end.
  • TIA registers are all emulated, with the exception of audio (AU*) and hardware test (RSYNC);
  • Every single aspect of the emulated code is spec-ed, except for some TIA parts I am still figuring out. CPU/RIOT are pretty final, TIA might still be refactored since it suffered a lot of crazy stabs due to the lack of documentation (see below).

Known issues

  • Objects rendered close to the left side (counter zeroing during HBLANK, I suppose) sometimes render in wrong positon (see diagonal.bin test);
  • Some games display an artifact at the left side (late hblank area) where there should be nothing (Freeway, Boxing);
  • Donkey Kong is rendered one pixel off its Stella position;
  • Seaquest renders a full scanline with the diver (missile?) at some specific situations. This bug and the three above it might be related (some odd rendering on a specific postion);
  • Enduro and Jawbreaker render their frames a few dozen scanlines after the right position (Enduro leaves some trash behind, Jawbreaker leaves a black space), cutting the lower part;
  • Maybe UI should dynamically adjust to games that (intentionally) generate larger/smaller frames;
  • Emulator only supports NTSC games - not sure if it's worth the hassle of supporting PAL/SECAM anyway, as most(all?) PAL games are NTSC versions, and SECAM, well… sucks.
  • Should display the logo during startup (either by overriding first few calls to frame, or by running a bootstrap ROM that shows it).

Technical debt:

  • MRI startup script could be improved (just quickly slapped a Gosu script on /bin to make it playable;
  • Add an Opal generation/running script (see this post on running ruby2600 on Opal)
  • TIA tests don't cover a few aspects (see "Pending" specs);
  • Bus should auto-initialize the components when receiving a string (either on initialize or on a separate method);
  • Maybe Player/Ball/Missile/Playfield should not be separate classes, since most of their code is "configuration" for the generic Graphic (and its Counter).
  • There is some overuse of "magic" in CPU tests (mostly syntatic sugar to keep them descritive). Maybe it should be more properly encapsulated (outside of the test class itself).
  • RSpec 3 would improve the semantics (and even make some of the aforementioned magic unnecessary)
  • Convert this list to Github issues.

FAQ

Why?

I had two (somewhat) unrelated goals for this year:

  • Learning more about the 2600 (to write a game in the future);
  • Getting more proficient with Ruby and RSpec.

The emulator is the way I found to tackle both at once.

Also, I wanted to test how well such tools coped with emulator development. Performance aside, it was a huge success: an emulator for a very tricky and under-documented system in ~3 months, written by someone that never wrote a full emulator before.

How good is it?

Not really good if your goal is playing games. Keep in mind that:

  • It is slow (~1/30 of a real Atari on my computer).
  • It has no sound.
  • It has a few glitches.

It is good, however, if you want to learn more about the 2600, as the lack of concerns with speed makes the code more accessible than the average emulator.

Will you make it faster/add sound?

Now that ruby2600 reached reasonable compatibility with general games (remaining glitches are unlikely to change the emulator structure in any aspect related to performance), it can be worked. See the slides mentioned above for some planned strategies.

If you want a full-speed emulator with sound and compatible with every single game under the sun, I wholehartedly recommend Stella - which has been an invaluable source of inspiration, debug help and implementation reference. It's what I use to play (other than my real Atari).

Is this logo renderable on an Atari?

Not sure, I did it one day I was too bored to write code or slides. I'd say yes (as a playfield), as long as you are flexible with the background color (or use another object to render the characters). But I liked it, so I might eventually try to make a ROM that displays it.

How to browse the source code?

The slides mentioned above will give you an overview of the core classes. If you understand Atari architecture well, you may start with Bus (which, unsurprisingly, connects all the pieces together). CPU is completely independent and may also be a good starting point.

Optimization Ideas

Here is a backlog of things that may help towards increasing performance:

  • ricbit suggested we try to cache the scanlines. Essentially, given a specific TIA (+CPU/RIOT?) state, the scanline generated will be pretty much the same, so we could skip everything if no TIA registers are changed throughout drawing. He mentioned hefty gains on BrMSX by doing so. We just need to ensure consistent state on internal counters (although we could add them as part of the mentioned state hash key and the final value on the cached scanline) and other detail like that.
  • Georges made some experiments with unrolling loops, finding a 2x increase by simply doing this. It seems cache-related (increasing from 16 to 32 unrolled cycles killed the improvement), but there may be some gains around here (oddly, both MRI and JRuby had improvements working that way)
  • We could easily do the color translation (currently done by the ui scripts) whenever a color register is written to, greatly reducing the number of lookups. We could also keep the more frequently used colors on a quick-access location (would not help with "rainbow" bound games, but quite a few stick to a reduced color set)
  • Lucas Fontes tried reusing the scanline array with no noticeable improvement, but we might try having a fixed "framebuffer" array and rewriting it constantly, to kill the instantiation payload, or even consider an alternative structure to store the bytes. UPDATE: The FrameGenerator class makes it easy to test those things; I did an initial attempt of reusing buffer / using NArray for each scanline, but no noticeable improvement was found (maybe MRI is being smart enough to use integer types where appropriate).
  • Even though advanced pipelining is hard to implement on old architectures like 650x, it would be theoretically possible to parallelize the fetch/decode/execute p

Related Skills

View on GitHub
GitHub Stars125
CategoryDevelopment
Updated6mo ago
Forks10

Languages

Ruby

Security Score

87/100

Audited on Sep 10, 2025

No findings