There is a playful way to study the architecture of computers of the past. Find a piece of software you know well and try to find out how it was ported to these machine you don’t.
A good choice would be DOOM. id Software’s 1994 mega-hit has been ported to everything. It is designed around a core with no layering violations. It is usually easy to find and read the implementation of its six I/O sub-systems.
An other choice would be Eric Chahi’s 1991 critically acclaimed” title “Another World”, better known in North America as “Out Of This World” which also happens to be ubiquitous. I would argue it is in fact more interesting to study than DOOM because of its polygon based graphics which are suitable to wild optimizations. In some cases, clever tricks allowed Another World to run on hardware built up to five years prior to the game release.
This series is a journey through the video-games hardware of the early 90s. From the Amiga 500, Atari ST, IBM PC, Super Nintendo, up to the Sega Genesis. For each machine, I attempted to discover how Another World was implemented. I found an environment made rich by its diversity where the now ubiquitous CPU/GPU did not exist yet. In the process, I discovered the untold stories of seemingly impossible problems heroically solved by lone programmers.
In the best case I was able to get in touch with the original developer. In the worse cases, I found myself staring at disassembly. It was a fun trip. Here are my notes.
Another World 101
There is very little code in Another World. The original Amiga version was reportedly 6,000 lines of assembly. The PC DOS executable is only 20 KiB. Surprising for such a vast game which shipped on a single 1.44 MiB floppy. That is because most of the business logic is implemented via bytecode. The Another World executable is in fact a virtual machine host which reads and execute uint8_t opcodes.
Another World VM defines 256 variables, 64 threads, 29 opcodes, and three framebuffers. That’s it. If you build a VM host capable of handling these, you can run the game. If you are able to make the VM fast enough to run at 20 frames per seconds, you can actually play the game.
The virtual machine’s graphic system uses a coordinate system of 320×200 with 16 palette-based colors. The color limitation may be surprising given that the development platform, the Amiga 500, supported up to 32 colors. This choice was a sweet spot allowing the graphics to be compatible with the other big platform of the era, the Atari ST which supports only 16 colors.
The limitation turned out to be a blessing in disguise. It resulted in an unique style which has aged well.
Even when it would have been possible to use a specific palette for certain scene, Eric Chahi elected not to do so. During the close encounter with “The Beast”, only three colors are used for the creature with black for the body, red for the eyes, and beige for the teeth. Imagination did the rest.
The palette system turned out to be a strength to illustrate the accident at the origin of Another World. A non-expensive palette swap is enough to evoke a lightning strike.
The engine is also capable of translucency effects if the scene features only eight colors.
Here the colors are stored within [0x0,0x8].
The Ferrari light beams are translucent. They are drawn with a special color 0x10 which does not exist since only 16 colors are available. The special value is interpreted as “read framebuffer index, add 0x8 and write back”. The last part of the trick is to cleverly chose the 8 next colors in the palette.
Translucency was not used very much in the game but it can be seen one more time during the experiment turning wrong cinematic when the lightning is about to teleport Lester to AnotheR WorlD.
The Three framebuffers
Of the three framebuffers, two are used for double buffering while the last one is used to save background (BKGD) composition. This is an optimization to avoid redrawing all the static background polygons in favor of a simple copy operation.
In the next video, see how a new scene is drawn first in the BKGD buffer. Each new frame, the BKGD is fully copied to the double buffer not being scanned by the display. There, moving elements such as Lester are drawn. Notice how once the car is “parked” it is also drawn in the BKGD buffer to minimize the number of polygons to render in subsequent frames.
Also notice how composition uses a plain painter algorithm which is simple but result in significant overdraw. This is not a problem since it is largely amortized by the BKGD copy.
Virtual Machine Opcodes
The next array illustrates the 29 opcodes. We can find here thread (THRD) management, framebuffer (FB) management, and all the register management operations. Most opcodes are “easy” to implement except for “COPY FB”, FILL, and “DRAW_POLY*” which are difficult for performances reasons.
Both DRAW_POLY_* opcodes span over more than one entry. In the case of DRAW_POLY_BACKGROUND, it takes half the opcode-space from 0x80 to 0xFF. It looks wasteful but it is a space saving trick. Using all opcodes starting with bit “1” allow the 7 other bits to piggy-bag in opcode space as drawing parameters. Since this opcode is used for backgrounds and cinematics, space saving is very significant.
The SPRITE version uses all opcodes starting with bits “01” while the other remaining 6 bits are here to encode x, y and zoom in order to draw “sprites” such as lester, friend, and enemies.
As mentioned in the previous section, 26 out of the 29 opcodes are easy to implement. The real challenge in porting this game was in manipulating pixels within the machine BUS and CPU bandwidth limitations. What will be done in this series is study how each port manipulated the framebuffers and how they solved the DRAW, FILL and COPY problems.
Ready? Let’s dive into Another World on Amiga 500.