Cloud Based Atari 2600
Posted: July 25, 2021
It seems like a lot of big companies are creating ways to play games "in the cloud". The games are in some way being rendering on big servers and the display is streamed to low power consoles. This concept, along with wanting an excuse to play around with Kubernetes, inspired me to create an Atari 2600 emulator that can stream the game in a similar fashion.
I planned to use a Turing Pi for the cloud system, but the Raspberry Pi CM3+ modules I ordered in March are still backordered. When I get them I have a second project almost ready to go to run on them. For this project I ended up using two Raspberry Pi 4's.
The emulator itself is complete enough to run Joe Davisson's Space Revenge game. It's an 8k game written in Java (Joe was the one who added 6502 and Atari 2600 support to Java Grinder) with no copyright issues, so I figured this was the ideal game to show off. There are some Atari 2600 features missing from the emulator so I think not many other cartridges will work, but that can fixed at a later time.
Below is a video demo of the system along with a link to the git repository and a long description of how the different components work.
The video above shows the two Raspberry Pi 4's along with the screen of a desktop x86 PC running 3 Atari 2600 ROMs in 3 Firefox windows. Unfortunately, due to the lighting and such, what's not visible is the browser address bar which would have shown each running game has a different IP address and port (depending on which of the two Raspberry Pis in the cluster it's running on and a unique port).
The Atari logo and Space Revenge come from Java Grinder and the heart comes from an Atari 2600 tutorial.
Related Projects @mikekohn.net
The biggest part of the project is the emulator which simulates the real Atari 2600 hardware. It's not exactly feature complete, but it's enough to prove the point of being able to play Atari games in the cloud. I wrote the code as quickly as possible because I was more interested in implementing the Kubernetes part of the project.
Key features of the emulator (currently):
What's currently missing:
What could be interesting is implementing the second joystick for a separate display so that two random people from around the world could play Atari games with with each other.
To make the game network played there were a number of options that could have been implemented:
I decided at first to just do VNC in this case. It requires a VNC viewing program and there's no sound, but it kind of seemed like the quickest to implement. Also the web interface seemed like it could have slower latency on the joystick presses if Connection Keep-Alive doesn't keep alive. The streaming as MPEG seems like it's probably closer to what the big cloud based gaming systems use and would have used a lot less network bandwidth than VNC.
Speaking of bandwidth, with some temporary debug code in the VNC server part of the code counting the number of bytes of data transmitted, The Space Revenge logo moving down and up the screen 1 time lasts about 10 seconds. The number of bytes sent when there is a full screen is around 565,506,080 (539MB). When incremental updates are sent it drops to 20,606,076 bytes (20MB). When the game is playing there should be quite a bit less data since only the ships and the missiles are moving. This seemed a bit excessive so I decided to test what some compressed images could get. Given that the display is 480 x 384 x 4 bytes, the sizes of the display are:
Running The Emulator
The cartridge used to develop the code is Joe Davisson's Space Revenge which can be downloaded here: space_revenge.bin. To run the emulator in SDL mode, where a window will open on the local machine, the following command can be run:
To run it with VNC on port 5900 (the default VNC port):
Then from the client system, to play the game:
To run it with HTTP on port 8080:
Some debuging features:
The debug mode will run the code while after every instruction dumping the current state of the 6502, chips, and RAM. The break mode will run like debug mode but stop when the CPU hits the provided address. The step mode works like break mode except allows the user to keep pressing enter to run one more instruction. The timer mode works like debug mode except when the PC hits the start address, CPU cycles will be added up until the PC hits the stop address. At that point the program will stop and print out many CPU cycles were executed.
To play the games the following keys control joysticks / switches:
I started the emulator by mostly rewriting an old partially written Atari emulator I did in Java a few years ago called Jatari. The emulator consists of a few modules: M6502, MemoryBus, TIA, RIOT, and Television. There are also some extra modules such as Nework to take care of all the socket code and GifCompressor that creates GIF images from the current Atari 2600 display. The GifCompressor module was a port (and cleanup) of libkohn_gif which I wrote back in 2006 or so.
M6502.cxx emulates the 6502 CPU of the Atari 2600 and is pretty much the center of emulation. A single function call to the M6502 will execute a single instruction and return the number of CPU cycles it would have taken on real hardware. Any memory read / writes are done by making calls to the MemoryBus module which routes the operation to either the TIA or RIOT and deals with the memory mirroring.
The TIA object emulates the Atari 2600 TIA graphic chip. It includes drawing the playfield, sprites (players, missiles, and ball), and video sync signaling. The chip itself runs 3 times faster than the CPU so for every CPU cycle used in the 6502, 3 ticks are applied to the TIA. The TIA keeps track of the x,y position of the video beam and draws the appropriate pixel to the location in an image buffer.
The RIOT object has a countdown timer, I/O switches including the joystick, and 128 bytes of RAM.
The Television object is an abstract class that is currently extended by TelevisionNULL (for no graphics display, good for debugging), TelevisionSDL (for displaying directly on the local display, which was also good for debugging before the more complex VNC module was done), TelevisionVNC, and TelevisionHttp. The Television class has a refresh() function which is called when TIA gets a VSYNC strobe. For most modules this will force the display to be drawn. The Television object can deal with 8 bit images (needed for faster processing of GIFs) and 32 bit images needed for SDL and VNC. The subclass of Television tells the TIA which buffer to write to.
For the VNC module, on a refresh() the code will look to see which scanlines have changed and will build up a "rectangle" list of the changes to send over the network to the VNC client. If too many lines have changed a full refresh will be performed. The handle_events() function will check for keyboard events on the network and relay them back to the main() function in cloudtari.cxx.
For the Kubernetes part, I set up two Raspberry Pi 4's with 64 bit Ubuntu and followed a tutorial to install microk8s Kubernetes on them. I created a docker image (a Dockerfile and Makefile for building it is in the repository in scripts/docker) that holds a compiled version of Cloudtari and 3 copyright-free cartridge files. It ended up that building Cloudtari under the host system creates a binary that doesn't work in the docker container (wrong library versions) so I created another docker container for building Cloudtari (scripts/docker_build in the repo). Still requires a git clone and removing the SDL module, but other than that it builds a working executable.
After creating the docker container I got it imported into Kubernetes using the Makefile and cloudtari.yaml file in the scripts/kubernetes directory of the repo. The Makefile also has a lot of other useful commands I needed.
On the main Kubernetes node I installed the nginx web server along with PHP. The index page serves up a choice of ROMs to play. When selected, a launch.php script starts a Python script called start_game.py which instructs Kubernetes to clean up old jobs, find a unique port, and create a unique cloudtari.yaml that is used to start a new job. In order to get the start_game.py script to work from PHP I had to add the webserver to sudoers for that script. There are defintely better ways to take care of that, but for now this works. The host / port which the game is running is passed back to the PHP script which redirects the browser to that server so the user can play the game. When the connection to the webserver is broken Cloudtari exits and the Kubernetes job ends.
The emulator needs a bit of work to be a full blown emulator that can play all Atari 2600 games. It would also be interesting to make it emulate other systems.. maybe Nintendo NES or something. Probably would need to move all the Atari pieces to an Atari2600 class and make objects for other systems that would be emulated.
Copyright 1997-2023 - Michael Kohn