When the existing machines no longer inspire you, it is time to switch to imaginary ones.
This article was originally published at Skrolli 2016.1E – International Edition.
This article in Finnish: Pico-8 – Fantasiakonsolin pauloissa
During their careers, many computer hobbyists have tried countless device and software platforms. Some have even surveyed them intentionally in order to play their games or use them to create art. This sort of exploration also creates an understanding of the features that make a platform comfortable or interesting to work with. The fantasy console Pico-8 is one idea of an interesting platform.
The Pico-8 is probably best described as an emulator for a system that does not exist. Its overall spirit is very 8-bit and you might envision it as a handheld console like the Game Boy Color. The screen offers a resolution of 128 × 128 pixels at 16 colours and there are four chiptune channels for sound.
However, this is not merely an exercise in alternative history; the design of the Pico had very different starting points than devices that might seem similar on the surface. Instead of trying to make the most out of a limited amount of logic, it offers a small set of building blocks that are as fun to play with as possible. The developer hopes that, over time, the technical framework of the Pico-8 would give rise to a unique aesthetic that would be expressive in spite of its minimalism.
The Pico-8 appears slightly schizophrenic. After start-up, it enters a mode that is more reminiscent of a home computer with a keyboard and mouse than a game console. The command interpreter allows for loading software and typing in print commands, for example. By pressing Esc, you can enter an editor that has dedicated sections for code, graphics and sound. However, only the internal applications can access the keyboard, mouse or all parts of the file system – to external applications, the Pico is a game console with ROM cartridges and a two-button pad controller.
Pico speaks Lua, a limited but fairly expressive language that was originally designed for game scripting. In other words, the Pico virtual machine does not emulate any processor in itself – not even one that executes bytecode. Everything is written in Lua, and this is the lowest level a programmer can access.
There are two main formats for distributing games and other software. The cartridges, or carts, are PNG pictures that have the appearance of a physical cartridge and sticker, but whose lower bits store the program code and graphics and sound data like a watermark. Software exported to HTML5 format can be run on modern Web browsers without the actual Pico-8 software.
The most visible technical feature of the Pico is the 128 × 128 pixel screen with a fixed 16-colour palette. The palette has a fairly personal and easily identifiable choice of colours, and its designer has clearly had more of an eye for colour than the average engineer.
Although Pico games typically use background maps consisting of 8 × 8 pixel blocks, and 8 × 8 pixel sprites on top of them, this is not a limitation. The graphics mode is a pure pixel buffer that can be used to draw anything – and the machine is also fast enough to run 1990s demo effects smoothly. However, the platform encourages the use of 8 × 8 pixel blocks by offering functions for drawing maps and sprites that are faster than using your own code to do the same pixel by pixel.
The cart can hold 15,360 bytes of compressed code. The maximum length in the editor is 65,536 characters or 8,192 tokens in tokenised form. These limits are not easily met – even many of the best games are clearly below these figures. On the other hand, the existence of this limit encourages simplicity and linearity, as there is no room for enormous game engines and multi-layered abstractions.
The cart has 12,544 bytes reserved for graphics and 4,608 bytes for sound. Of course, these data areas can be used for other purposes – the memory handling commands allow it to be accessed at the byte level. Upon program start-up, the contents of the cartridge’s data side are copied into user RAM where the program can modify it, if necessary. User RAM also includes slightly under 7 kilobytes of space reserved for the user and 8 kilobytes of video memory.
The graphics data consists of 8 × 8 pixel sprites where the entire colour palette can be used freely. The maximum number of sprites is 256 and the map consists of 128 × 32 sprites. It is possible to double the size of the map by settling for 128 sprites.
On the sound side, the equivalent of a “sprite” is a sound effect (sfx) that consists of 32 note locations. Each note location contains the note and the waveform, volume and effect; there are 8 different types of each of these. The playback speed can be altered; lower speeds are better suited for music than sound effects.
Similarly to tracker music, a song consists of patterns that define which sound effect is played on each of the four channels. There is space for 64 patterns, which can hold several songs when loops and pattern end flags are used.
While the graphics side allows everything to be built from individual pixels, the user cannot access the “registers” of the sound system. In theory, you could build a player routine by modifying the sound data in real time, but the limits of the virtual machine’s timing might not allow this. However, you can easily create different experimental soundscapes by writing random data in the sound effect memory.
In addition to the program code, data RAM and cartridge ROM, the Pico offers 256 kilobytes of space for the Lua interpreter. This is a relatively large amount when compared to the Pico’s other memory spaces, but it will fill up easily with large tables, for example. One element of a number table takes up eight bytes, half of which is taken up by the actual data. Each number consists of a 16-bit integer part and a 16-bit fraction, which means that bit arithmetic operations can be used to compress them.
When running low on space, Pico can also read data from other ROM cartridges and even write to them. Program code has a strict limit, however; it can only be executed from the original cartridge. Lua in itself includes the possibility to execute data as code, but it has been removed in the Pico-8. This means that those requiring more code space will need to build their own virtual machine.
The Pico-8 does not execute Lua code as quickly as the computer’s processor allows. Execution times have been defined for the different functions. This speed limit will rarely lead to problems during the development of typical Pico software, but it standardises the limits of the platform and prevents spiralling hardware requirements. According to the authors, a first-generation Raspberry Pi is enough for running even the most demanding Pico software at full speed.
Lua is written in all capitals and is, therefore, reminiscent of BASIC. For example, many people will remember the BASIC version of the following infinite text printout loop:
Instead of the Lua standard library, Pico offers a fairly limited selection of BASIC-type functions: drawing commands, a couple of sound commands, controller input functions and a few functions for memory handling, mathematics, bit arithmetic and string handling.
The basic drawing commands can be used to draw pixels, rectangles, lines, circles, text, sprites and background maps. The palette colours can be switched for the drawing commands and you can also make colours transparent in terms of the sprites and background graphics.
Drawing moving graphics is more reminiscent of a PC than the 8-bit home computers. The Pico has no “hardware sprites” or “hardware scrolling”. Instead, the display is usually redrawn for each refresh: clear the screen, draw the background and then draw the necessary sprites.
There are two functions available for drawing sprites: spr() draws an individual 8 × 8 pixel sprite at the provided coordinates, whereas sspr() draws an arbitrary area from a sprite sheet at arbitrary scaling. The scaling function enables a number of tricks that are fairly costly on most classic hardware, such as Doom-type texture mapping.
The programmer may place drawing commands in an endless loop, but a more elegant solution is to define a function called _draw() and call it at every screen refresh, i.e. 30 times per second. The earlier example would appear as follows:
The game controller is read with the function btn() that accepts the button number as a parameter and returns whether the button is pressed. A program that moves sprite number 0 to the left and right might look like this:
IF BTN(0) THEN X=X-1 END
IF BTN(1) THEN X=X+1 END
In order to display anything on the screen, you of course need to draw something for sprite 0 in the sprite editor.
Sometimes, _draw() will contain so many tasks that it cannot be run at every screen refresh. In this case, the programmer should move the updating of the game state to the _update() function that is – theoretically – called 30 times per second. Theoretically, because it is not a timer interrupt; if _draw() takes longer, it is called several times in a row.
Double-buffering is not a concern, as the changes to the video memory will only appear after _draw() has been completed. On the other hand, the user RAM could only fit a full-screen double buffer if some of the sound effects were cleared out of the way.
The sound side offers the functions sfx() and music(). The former plays the sound effect given as a parameter on the first available sound channel, the latter starts playing music from the pattern number given as a parameter.
Makers of small 2D games need not concern themselves with the speed of the commands, but it will become an issue when testing the limits of the platform. The function pset() refreshes all the pixels on the screen approximately one and a half times during one screen refresh. Writing directly into video memory with the poke() function is about three times faster. However, using the memcpy() and memset() functions is up to ten times faster, and the background graphics drawing command map() is equally fast.