Rocket Man Disassembly
This page presents a commented disassembly of the classic 16K ZX81 game Rocket Man written by Julian Chappell and published by Software Farm in 1984.
Rocket Man is written entirely in machine code and uses a custom display driver routine to achieve pseudo high resolution graphics on a standard 16K ZX81. It features smooth animation, with each sprite consisting of multiple frames that shift by just 2 pixels at a time, and contains 6 different levels. Each has a different platform layout, and for levels 4 to 6 the rocket pack and fuel cans are replaced with a vulture and legs of lamb. There are three alternate graphic sprites used for the Bubloid, each with its own acceleration speed, and these are cycled through as the player progresses up the levels.
According to Julian (in an extension to the interview with Villordsuch in 2017):
"Rocketman sold quickly upon release. It sold enough to catch up with Forty Niner in a shorter length of time, so it did sell faster but not necessarily more."
The game proved so popular that it appeared 28th in a Gallup survey of top games (Source: Sinclair User magazine, January 1985, page 142), which was an amazing achievement for a ZX81 game when the charts were otherwise completely dominated by Spectrum and Commodore 64 titles.
The inlay artwork for the cassette contributed to the feeling of quality for the game, making it stand out amongst the other titles published for the ZX81:
"Those first low-res games, where I designed and drew the artwork myself, sold in sufficient quantities to be able to afford a professional graphics designer. The artwork [for the hi-res games] was done by the Wright brothers, who lived in a religious commune nearby."
The machine code is stored within nine REM statements, with functions divided as follows:
- Line 1 - The pseudo hi-res display file.
- Line 2 - The pseudo hi-res display driver, sprite graphics, screen definitions, program variables.
- Line 3 - Message table, key table, screen display routines, redefine keys routine, fuel can / leg of lamb placement routine.
- Line 4 - Handles player movement and display when running / jumping.
- Line 5 - Handles player movement and display when flying by rocket pack / vulture.
- Line 7 - Handles movement and display of the Bubloid.
- Line 8 - Draws fuel guage, draws bonus, player caught animation, sea animation, splash animation, handles all lives lost.
- Line 9 - Draws fuel can / leg of lamb.
- Line 10 - Fuel can / leg of lamb placement (continuation), wait for key to start level, pause and restart functions.
The game consists of 6 different levels. A table is used to hold the definitions of their layouts, with each described using the following format:
- 1 byte: Number of platforms.
- For each platform:
- 2 bytes: Display file address of the left end of the platform.
- 1 byte : Width of the platform.
- 1 byte: Number of ladders.
- For each ladder:
- 2 bytes: Display file address of the top of the ladder.
- 1 byte : Number of rungs forming the ladder.
The encoding format allows a screen layout to be defined using very few bytes, e.g. level 1 is specified using just 56 bytes.
Fuel cans and legs of lamb are placed on the platforms at random locations. A column is randomly chosen using the value from the FRAMES system variable and a search is performed at this column downwards to try to find a platform. If one is found then up to 3 columns to the left are checked for a free location, i.e. above a platform and not clashing with the player. If a suitable location is not found then a new column is chosen and the search process repeated until it eventually succeeds.
Background buffers are maintained for both the player and the Bubloid. They hold the screen contents at the locations where the player and Bubloid sprites are drawn. As a sprite is moved, the locations it has moved out of are restored with the graphics stored in the background buffer. The buffer contents are then shifted and updated with the graphics from the screen locations the sprite is moving into.
To check whether a sprite can move, the screen or background buffer contents are examined, e.g. for the edge of the screen or for the player being in front of a ladder graphic. A collision between the player and the Bubloid is done by checking for any overlap between the screen locations they occupy. This approach is also used when checking whether the player has collected a fuel can, leg of lamb or diamond.
The Bubloid hunts by always attempting to move towards the player. It will build up speed as it continues to move in the direction of the player, until a maximum speed is reached. However, if the Bubloid needs to change direction then it takes time for it to decelerate before it can reverse direction and begin accelerating again. Seperate acceleration values are maintain for the horizontal and vertical directions.
Flying via the rocket pack is also subject to acceleration. If the rocket pack is not fired to move upwards then the player's speed will decrease until eventually the player stops moving up and instead begins to accelerate downwards.
The game controls its timing by monitoring the FRAMES system variable. This approach guarantees that the speed of the game is constant no matter what actions are performed. Key presses are detected via system variable LAST_K.
The game uses self modifying code in a few places, thereby allowing the behaviour of several routines to be adjusted based upon the level, e.g. to increase the Bubloid acceleration as the player moves through the levels.
The game contains a number of bugs:
- After losing a life and restarting the current level, the game fails to reset the timer for the sea animation and so it can take several seconds for the sea wave to begin to move.
- After losing a life while flying via the rocket pack or vulture, the game fails to re-instate the rocket pack / vulture back into the screen. If the player still has fuel remaining (and avoids picking up any more) then it is possible to jump into the empty location where the rocket pack / vulture usually resides and to switch to flying it.
- It is possible that the Bubloid is in mid air at the same height as a platform level and gets mistaken for a platform when placing a fuel can / leg of lamb. This leads to the fuel can / leg of lamb being placed in mid air.
- If the Bubloid is partially overlapping the fuel can / leg of lamb as the player collects it from the other end then an artefact of the fuel can / leg of lamb will be left on the screen.
The game also contains a number of issues that aren't technically bugs but are instead shortcomings in the implementation:
- The sea animation does not extend into the final column of the display. The wave graphic is made up to alternating segments. A flag is maintained that determines which segment type to display next. By only displaying 31 columns, i.e. an odd number, the flag is guaranteed to end up in the opposite state to how it began, and hence next time the wave is drawn it will begin in the opposite phase. Ideally the wave should span all 32 columns and the game just toggles the flag one additional time after displaying it so that it still ends up in the opposite phase to how it started.
- The player always ends up falling into the sea after losing a life and causing a splash. The splash animation ends by being blanked out, but this leaves a gap in the sea. Ideally the game should have redrawn the sea graphic after the splash animation finished.
- When a game ends, a normal resolution screen is shown listing the keys and an option to redefine them. The keys shown are hard-coded in the display messages and no attempt is made to replace them after the player has redefined the keys.
The high resolution display driver routine used by Rocket Man was originally developed by Julian for Forty Niner, and came about almost by chance:
"I didn't start exploring the inner architecture of the ZX81 with the intention of creating hi-res graphics. At that point I didn't know what was going to be possible - it was more of a case of simply hoping that I would find something useful rather than looking for something specific, but when I found out how the graphics system worked it was just begging to be tweaked!"
Julian's display driver routine was created independently of the one released earlier in 1981 by Macronics,
and contains a significantly different structure which is far more compact. Julian states
I had never heard of Macronics! and so it can be concluded that the technique for pseudo
high resolution graphics was devised on several occasions by different people.
Exploiting the pseudo hi-res technique for a game introduced new challenges:
"The only part which took longer than writing a 'normal' ZX81 game was the extra complexity of animation using bit patterns moving across a character position rather than simply displaying a single character. As the hi-res system worked by redirecting the character map into the ROM all there was to work with was a load of random bit patterns. A suitable one had to be searched for every time. That, I must admit, was rather fiddly and time consuming!"
Where he could, Julian re-used code from Forty Niner:
"Coding for Rocketman was much quicker than for Forty Niner as the graphics system was already worked out, and even some of the animations."
Although the graphics for player sprite were re-used from Forty Niner, Julian didn't consider it was the same character from the previous game just now in a new setting (such as Willy from Manic Miner and Jet Set Willy), and as a result:
"It never occurred to me to give the character a name. Damn! I wish I had now!"
The machine code was assembled by hand and entered manually:
"With only 16K there wasn't room for a compiler and the program being written, so I developed a small load routine which wrote bytes typed in hex format into consecutive addresses in the RAM. Yes, the whole program was typed in manually a byte at a time and then saved on a reel-to-reel [tape recorder]."
Considering this fact, the program is remarkably well structured and contains very few patches inserted to correct bugs detected after the program bytes had been entered into memory. There are a few remnants of old routines scattered throughout the code, but these are just fragments and don't total up to many bytes. Their presence though is indicative of a program having been hand assembled, just as Julian describes.
Rocket Man was, and still is, regarded as one of the true classic games for the ZX81, but developing it came at a cost for Software Farm:
"The second the Spectrum was known to be on the horizon all companies dropped the ZX81 like a hot potato and raced each other to be the first to have Spectrum games available for when it materialised. I believe I was half-way through writing the second hi-res ZX81 game at the time, so had the choice of abandoning it and joining the herd, or stick with it. Sticking with the ZX81 to release Rocketman was a risk. It turned out to be commercial suicide."
But fortunately Rocket Man was not to be the last pseudo hi-res game from Software Farm:
"I started to get games sent in by hopefuls wanting to get published. I didn't write Z-Xtricator, or any of the other ZX81 titles that followed Rocketman."
Out of the games Julian wrote for the ZX81, the one he is most proud of is...
"Rocketman of course! Because it is such a brilliant game (even though I say so myself!) It has all the elements that game designers strive for. All features of the game work so well together, making it both addictive and fun to play."
I doubt there are many ZX81 users that would disagree.
|Click here to download the commented disassembly of Rocket Man (dated 6th August 2017).|
|Click here to access the program file for Rocket Man on Simon Holdsworth's excellent ZX81 archive website.|
Using the disassembly, I've created Colour Rocket Man which allows Rocket Man to be played in colour via the Chroma SCART Interface. I've also created a conversion of Rocket Man for the Spectrum and a conversion of Rocket Man for the SPECTRA interface.