Hyper Rally

A twelve-stage rally in a 16 KB cartridge: a fake-3D road, a dashboard with a speedometer, fuel, gear and clock, rival cars that collide by depth, and everything driven by the per-frame interrupt.

Konami · © Konami 1985 · An RC-718 16 KB cartridge · MSX1 · page 1 · Dump eca2c0d6…

There is not one illustration or capture here. The font and the tiles are drawn from the bytes of the ROM, by running in Python the same script decompressor the Z80 runs; even the road scene is built that way, running the cartridge's decompressors and painting the cars with its own patterns. The listing and the numbers come from the binary and are reproducible with make.

The cartridge in numbers

100%of the binary explained
428routines identified
12stages of the rally
6,446bytes of code
9,938bytes of data
0bytes unidentified

What turned up when we took it apart

It carries Konami's hidden mark

At the end of the ROM, behind the filler, Konami hid in many cartridges its catalogue number and the title in katakana; Manuel Pazos (@ManuelPazosMSX) found it. Here it is: the last eleven bytes from 0x7FF5 are the title reversed, its length, the 18 of RC-718 in BCD and the 0xAA that closes it.

One dead loop, and the whole machine in the interrupt

INIT hooks the interrupt to 0x4051 and falls into a jr to itself. From there the main program does nothing: each frame the interrupt reads 0xE000 (the state, 0 to 8), jumps through a table of nine handlers, and inside each one the byte 0xE001 splits the sub-states with djnz chains.

The road is a table of shapes indexed by curvature

There is no geometry: 0x68D0 reads 0xE074 —the curvature and position of the track— and indexes the table at 0x767C to pick which road shape to draw. The stripes move toward the player by scrolling a buffer (0x707A) at the speed's pace, and the depth scaling of the roadside objects comes from the tables at 0x6CD5.

Twelve stages from eight composers, and two of them at night

0xE060 runs from 1 to 0x0C and with it, through the table at 0x481A, the background composer of each stage is chosen: eight routines cover the twelve, and stage 12's composer calls stage 5's. The parameter 0xE061 (table at 0x4372) is a bit field holding what is particular about each stage: bit 1 is the slippery snow, 0x01 the tunnel, 0x10 the storm and 0x08 the night. In the two night stages, 0x71AC runs a field of sixteen stars across the sky, at pixel precision and at the pace of the car's speed.

The sound is the house's three-channel player

The engine at 0x5FB7 walks three fourteen-byte records (0xE010), reads the melodies with its command interpreter and writes the PSG through the BIOS. It is the same player framework Konami spread across its MSX cartridges; 0x5ED9 starts a melody or an effect by its number, checking priority first so it does not step on what already plays.

The player car is six sprites, and rivals collide by depth

The car is assembled from six sprites (template at 0x66E2) that 0x65FA repositions every frame from the wheel. The three rivals keep their records at 0xE138/48/58; 0x7C34 sorts them by nearness before drawing, and 0x7F89 halves your speed when you hit one.

An opponent has no path of its own

0x7CCB was in the listing as the impact, and it does not touch your speed: the only thing it writes is the opponent's position, to which it adds (the opponent's speed − yours) / 16. In a 45-second race without a single hit it ran 1329 times. And 0xE09C, which the listing called the opponent's colour, is its speed: forced to 0 you eat all of them, forced to 255 they all get away. The arithmetic redone in Python reproduces 102 of 102 cases read inside the routine itself.

There is no pyramid drawn anywhere in the ROM

In the desert the road does not scroll: 0x707F diverts the scroll to SUBEN_PIRAMIDES. And there is no pyramid stored anywhere: there is a 32-byte window at 0x7351 —sixteen zeros, then the triangle— that slides one step each time 0xE071 reaches one of the sixteen thresholds at 0x7371. The right half of the pattern is the left one through INVIERTE_BITS, the same trick as the rival car.

An opponent coming closer changes material on the way

It does not grow: it stops being sprites and becomes screen. Table 0x7D94 gives twenty steps, and the two low bits of each pattern say how many sprites light up —one far away, two closer, and four in two overlaid colour layers, which is how you paint a two-colour car out of sprites that only take one colour each—. When byte 0 of the record drops below 0x28, 0x7B21 writes Y = 0xE0 into all four sprites and RIVAL_MEDIO (0x7AD1) repaints it with twelve tiles of the name table.

And of those twelve, the cartridge only stores half: 0x2A is 0x24 with its bits reversed, and so on up to 0x2E. The other half is put there by DESC_DOBLE (0x44B0), which runs the same block twice and sends the second pass through the relocated core at 0xE310, which falls into INVIERTE_BITS.

What the cartridge draws

Under each picture is where it comes from and what is on it.

The title screen, built with the cartridge's own steps: DIBUJA_MARCO (0x4D8E) decompresses the font into the pattern table and lays out the colours, DIBUJA_PANEL_JUEGO (0x4878) fills the rest of the colour table, and the script at 0x4C9E -the one PREPARA_PANTALLA_CARRERA dumps- paints the wordmark on rows 5 to 7 and the two lines of text with the font's glyphs
The title screen, built with the cartridge's own steps: DIBUJA_MARCO (0x4D8E) decompresses the font into the pattern table and lays out the colours, DIBUJA_PANEL_JUEGO (0x4878) fills the rest of the colour table, and the script at 0x4C9E -the one PREPARA_PANTALLA_CARRERA dumps- paints the wordmark on rows 5 to 7 and the two lines of text with the font's glyphs
The cartridge's font, drawn from the ROM by <code>tools/graficos.py</code>, decompressing the script at 0x4DEA with the same format the Z80 uses. Digits, symbols and the alphabet the whole game is written with, including the km/h unit of the speedometer
The cartridge's font, drawn from the ROM by tools/graficos.py, decompressing the script at 0x4DEA with the same format the Z80 uses. Digits, symbols and the alphabet the whole game is written with, including the km/h unit of the speedometer
The road and scenery tiles that follow the font in the same block, decompressed the same way. They are the pieces the track edges and each stage's landscape are built from
The road and scenery tiles that follow the font in the same block, decompressed the same way. They are the pieces the track edges and each stage's landscape are built from
How a rival comes closer, over the race screen built by running the cartridge's own decompressors. The same car at four distances: far away it is sprites -one, then two, then four in two colour layers- and on the last step the cartridge switches them off (0x7B21, Y = 0xE0) and repaints it with twelve screen tiles
How a rival comes closer, over the race screen built by running the cartridge's own decompressors. The same car at four distances: far away it is sprites -one, then two, then four in two colour layers- and on the last step the cartridge switches them off (0x7B21, Y = 0xE0) and repaints it with twelve screen tiles
The steps of a rival, read from table 0x7D94: the base pattern and its height on screen. The two low bits of that pattern say how many sprites light up, because 0x7A73 switches off the spare ones; the last one is not a sprite any more, it is four by three tiles
The steps of a rival, read from table 0x7D94: the base pattern and its height on screen. The two low bits of that pattern say how many sprites light up, because 0x7A73 switches off the spare ones; the last one is not a sprite any more, it is four by three tiles
The twelve stages of the rally, built by running the cartridge's own decompressors. Eight background routines cover the twelve, and the address under each one is what table 0x481A picks. Checked against twelve VRAM dumps from openMSX, all twelve match: the only thing that moves is what the game repaints every frame
The twelve stages of the rally, built by running the cartridge's own decompressors. Eight background routines cover the twelve, and the address under each one is what table 0x481A picks. Checked against twelve VRAM dumps from openMSX, all twelve match: the only thing that moves is what the game repaints every frame
The two effects painted over the background. In the desert the road does not scroll: pyramids rise, and there is no pyramid drawn anywhere in the ROM —there is a 32-byte window at 0x7351 that slides one step per threshold—. And in the storm three shapes of bolt fall, painted by PINTA_ROTULO from row 2
The two effects painted over the background. In the desert the road does not scroll: pyramids rise, and there is no pyramid drawn anywhere in the ROM —there is a 32-byte window at 0x7351 that slides one step per threshold—. And in the storm three shapes of bolt fall, painted by PINTA_ROTULO from row 2
The rival up close and its twelve tiles one by one. The twelve numbers are literal in the ROM at 0x7BF3, but only half of the patterns are: 0x2A is 0x24 with its bits reversed, and so on up to 0x2E. The cartridge stores half a car, and the other half is put there by DESC_DOBLE (0x44B0) sending the same block through INVIERTE_BITS
The rival up close and its twelve tiles one by one. The twelve numbers are literal in the ROM at 0x7BF3, but only half of the patterns are: 0x2A is 0x24 with its bits reversed, and so on up to 0x2E. The cartridge stores half a car, and the other half is put there by DESC_DOBLE (0x44B0) sending the same block through INVIERTE_BITS