Konami's Ping Pong

A table whose right half is not stored anywhere -it is drawn by mirroring the left one byte by byte-, forty frames each carrying the pointer to its own patterns right behind it, and the whole of table tennis scoring written in BCD.

Konami - (c) Konami 1985 - An RC-731 16 KB cartridge - MSX1 - page 1 - Dump 8e0f57b2...

Not one capture here. Every picture is drawn from the bytes of the ROM, by running in Python the same two script interpreters the Z80 runs. The listing and the numbers are reproducible with make, and reassembling gives back the ROM byte for byte.

The cartridge in numbers

100%of the binary explained
548named routines
64.3%of the listing commented
7,676bytes of code
8,708bytes of data
0bytes unidentified

What turned up when we took it apart

The right half of the table is not stored anywhere

The court is drawn once, and the other half comes from mirroring it. The routine at 0x7060 copies 768 bytes from VRAM to VRAM, passing each one through 0x47A6, which reverses its eight bits with eight rr c / rla pairs. The loop that calls it restores the source with a pop on every pass and only advances the destination, so the same chunk is mirrored into all three thirds of the screen.

You can see it in the pattern sheet: where the first third reads KONAMI, the mirror reads IMANOK.

Forty frames, each carrying the pointer to its own patterns

The players have no graphics table. Each frame is [dy, dx, pattern] per sprite, an 0x81 if it uses fewer than the loop asks for, and right behind it, the pointer to its own compressed block. The decompressor at 0x5E9B is not handed the block: it fetches it from there.

That pointer accounts for the 3,135 bytes of 0x73A7..0x7D25, and it checks out two independent ways: chaining blocks from 0x73A7 lands exactly on 0x7D26 in forty steps, and the forty pointers held by the frames are, one by one, those forty starts. Twenty for the near player and twenty for the far one.

The whole of table tennis scoring, in BCD

The score is kept in binary-coded decimal: 0x6760 adds with add a,001h / daa. Read that way, the rules come out complete, and they are the real ones:

The two-point-lead check accepts 0x02 and also 0x08, and that is not a bug: the subtraction is binary over BCD, so 21 minus 19 gives 0x08 and is still two points.

A sine table for the ball

0x6EAD holds 92 bytes that climb from 12 to 255, come back down, pass through zero and climb again. That it is a sine is measured, not assumed: the 31 values of the first quarter against 255·sin(i·90/31) are off by 4.7 at most, over a range of 255.

And the two places that read it, 0x6E99 and 0x6EA3, sit 31 entries apart: a quarter of a wave. One table gives both sine and cosine.

Dispatch happens on two levels, and the second uses no table

The scene is dispatched by 0x4062 through a table glued right behind the call: the pop hl retrieves its own return address, which is where the table starts.

But the SUBscene uses no table at all: it is picked by walking down a chain of djnz, two bytes each, and the block that runs is the one where B hits zero. Twenty-five steps in fifty bytes.

A label interpreter with four doors

One loop covers all four combinations of what can vary: whether it reads the destination address from the script or gets it ready-made, and whether the mask lets the bytes through or writes zeros.

The fourth is the clever one: with the mask at zero, the very script that wrote the label erases it, keeping its shape.

Perspective, with multiplication and a division done by hand

The Z80 can neither multiply nor divide. 0x6BA1 turns the three table coordinates into a screen row and column using two routines of its own -0x6F65 multiplies by adding and shifting eight times, 0x6F88 divides by subtracting and shifting sixteen- and three fixed factors: 0xD4 for depth, 0xBF for width and 0x8E for the row.

The ball's size is not computed: it comes from two tables of five patterns, picked by testing its height against four thresholds.

Konami's hidden mark

The last fifteen bytes of the cartridge are the title in katakana -コナミのピンポン, Konami no Ping Pong-, its length 0x0C and the 0x31 of RC-731. Before them sit 70 bytes of 0xFF: whatever was left over of the cartridge.

That mark was discovered by Manuel Pazos, and the finding is his.

The same skeleton as Road Fighter

Not similar: the same, piece by piece. The dispatcher with the table glued behind it, the two 8-over-16-bit adds, the label interpreter, the decompressor and its thirds loop.

And the eight VDP register bytes are identical, one by one: 02 E2 0E 7F 07 76 03 E4. They lay out VRAM the other way round from the usual -COLOUR low, at 0x0000, and PATTERNS high, at 0x2000- because R3 and R4 are not addresses but a base and a mask.

Two writes that go nowhere

0x4013 writes to 0x40EE and 0x4052 to 0x40F8. Both addresses fall inside the ROM itself, which cannot be written: the two lds run on every game and change nothing.

What the cartridge draws

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

The title screen, assembled step by step the way the cartridge does it. The logo is written by 0x4B93 one column at a time from the right, and the
The title screen, assembled step by step the way the cartridge does it. The logo is written by 0x4B93 one column at a time from the right, and the "O" in PONG is not a tile: it is two same-coloured sprites in the same place. Checked against the emulator's VRAM.
The table. Its right half is not stored in the cartridge: it is drawn by mirroring the left one byte by byte. Zero bytes different from the emulator's VRAM.
The table. Its right half is not stored in the cartridge: it is drawn by mirroring the left one byte by byte. Zero bytes different from the emulator's VRAM.
The level menu, drawn on top of the table once it is up. The five values -1, 3, 5, 7 and 9- come from the table at 0x461F, and the cursor is pattern 0x88 placed at the column it names.
The level menu, drawn on top of the table once it is up. The five values -1, 3, 5, 7 and 9- come from the table at 0x461F, and the cursor is pattern 0x88 placed at the column it names.
The pattern sheet of the middle third. On top, the font: 45 tiles from 0x10, which is the code for '0'. Below, the table and its mirror: where one reads KONAMI, the other reads IMANOK.
The pattern sheet of the middle third. On top, the font: 45 tiles from 0x10, which is the code for '0'. Below, the table and its mirror: where one reads KONAMI, the other reads IMANOK.
The company logo, which 0x4A2F draws one row higher on each pass. It is not a stored image: it is three runs of CONSECUTIVE patterns -three from 0x40, eleven from 0x43 and twelve from 0x4E- plus twelve zeros that wipe the previous pass. Zero bytes different from the emulator's VRAM.
The company logo, which 0x4A2F draws one row higher on each pass. It is not a stored image: it is three runs of CONSECUTIVE patterns -three from 0x40, eleven from 0x43 and twelve from 0x4E- plus twelve zeros that wipe the previous pass. Zero bytes different from the emulator's VRAM.
The sprite pattern table as it stands on the title screen: the balls at their five sizes, the net and the bats. The players are not here: their forty frames are decompressed one at a time, as needed.
The sprite pattern table as it stands on the title screen: the balls at their five sizes, the net and the bats. The players are not here: their forty frames are decompressed one at a time, as needed.