Konami's Golf

A nine-hole course in 16 KB, where every hole is stored once and read two ways, the graphics are worth double because the cartridge mirrors them on the fly, and the wind is drawn from the memory refresh register.

Konami · © Konami 1985 · An RC-723 16 KB cartridge · MSX1 · page 1 · Dump 6c539f0a…

There is not one illustration or capture here. The wordmark, the golfer and all nine courses are drawn from the bytes of the ROM, by running in Python the same decompressors the Z80 runs, and checked byte for byte against the emulator's VRAM: zero differences across 385,199 bytes, spread over 30 dumps. The listing and the numbers come from the binary and are reproducible with make.

The cartridge in numbers

100%of the binary explained
558routines identified
9holes, par 36, 3,512 m
8,665bytes of code
7,719bytes of data
0bytes unidentified

What turned up when we took it apart

One hole, one block, two readers

Each hole's data is stored exactly once, and the cartridge reads it two different ways: as a VRAM script it paints the plan view on the right, and as data 0x53B2 unpacks it into a 12 by 18 grid in RAM, which is the one that knows where the fairway is and where the bunker.

What hooks the two readings together is the last byte of the header: an 0x80, which in the interpreter means «change destination», and whose two destination bytes are the start of the grid. So painting from the first pointer paints the whole hole. Across all nine, the plan's 144 cells agree with what the grid says is there.

The same script draws the tile and its mirror, by not doing what it looks like

The script interpreter has two doors: one writes to VRAM, the other (0x601E) reads the same format against RAM. With that, 0x5FE2 paints a course tile and then the same tile mirrored left to right (0x5FFD), without spending one more byte.

And the mirror comes out of a slip turned to advantage. 0x5FFD looks like a transpose — eight rounds of rl (hl) and rra, which is how you transpose a bit matrix — but the inc hl at 0x600E sits outside the inner loop: the eight rotations keep hammering the same byte, which by the eighth is back where it started, while rra collects its bits from the other end. What comes out is that row with its bits reversed. The dump shows it: where the script at 0x60D6 puts F0, the cartridge leaves 0F.

And the finish: 0x6013 writes a 0x00 after the mirrored row and 0x601B an 0x88 in front, which is the «eight literal bytes» order. So 0x5FFD does not hand back some bytes: it hands back a finished VRAM script, which is why the original and its mirror can be painted by the same routine.

The wind comes out of the memory refresh register

At the start of every hole, 0x4932 does ld a,r. R is the DRAM refresh counter: the Z80 keeps it itself and it advances with every instruction, so its value at any given moment is unpredictable. Both things come from there: the low three bits plus one give the wind's strength (1 to 8) and the next two its direction (0 to 3). No random generator, no seed, and no table taking up room.

There is a practical consequence: the wind is the one thing in this cartridge you cannot predict by reading the ROM. That is why its cell is blank in the pictures below, and why checking them against the emulator meant feeding in whatever each dump happened to have in 0xE104 and 0xE105.

The ROM is stitched together with deliberate overlaps

Almost every table in this cartridge is indexed from 1, and entry 0 — the one never used — is laid on top of something else. Not an oversight: it is not spending the two bytes.

0x79BA falls on the golfer's last frame; 0x5B1B on an ld a,(hl); 0x5BDC on a jr; 0x5470 on a ret that doubles as the base of a rotation table; 0x7538 on the last two note periods; and 0x4D3B on the tail of a script.

The screen's three thirds, with a single copy

The MSX's SCREEN 2 is three independent thirds, and for all three to hold the same thing you have to write it three times. 0x4322 does it with one overlapping ldir: it copies 4,096 bytes from 0x0000 to 0x0800. Since the destination runs 0x800 ahead of the source, once the copy passes the first block it is reading what it has just written itself. One ldir instead of two.

The ball's height is not stored: it is the gap between its two sprites

The ball is two overlapping sprites, one black and one white. The black one stays stuck to the ground and the white one rises with the parabola, so the gap between them is the height. There is no height variable anywhere: there are two coordinates, and the height is their difference.

And that same difference gives the size it is drawn at. 0x7074 does ld a,(0xE0B0) / sub d — the lower layer's y minus the upper one's — and compares it against 0x21, 0x0F and 0x06: four ball sizes, from the fattest to the smallest. The higher it flies, the smaller it looks. On landing, 0x701B brings the two layers back together and the height is zero again.

One curve for both sine and cosine

The shot's direction does not use two tables: it uses one 66-byte curve at 0x52BF, read at two points 0x21 apart. Cosine and sine are the same table shifted by a quarter turn, which is exactly what they are.

It does not carry Konami's hidden mark, and that was checked properly

At the end of many cartridges Konami hid its catalogue number and the title in katakana; Manuel Pazos (@ManuelPazosMSX) found it, and the block lives at offset 0x3FF0. This one does not have it, and not for want of looking: all 16,384 positions were scanned with a finder first validated against cartridges of the same family that do carry it — the RC-718 and RC-729 of this series give theirs up on the first try — which is the only way to trust a negative. The dump shows why: the ROM runs full to 0x7FFD with just two bytes of filler left. No room.

What the cartridge draws

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

<b>The course's nine holes</b>, each with its panel: number, par, length in metres and the flag placed where the cartridge says. These are not captures: Python paints them with the Z80's own script interpreter, following the pointers at 0x79BC. That they are right is not an impression — the plan was compared cell by cell against thirty openMSX dumps, with <b>zero differences</b>
The course's nine holes, each with its panel: number, par, length in metres and the flag placed where the cartridge says. These are not captures: Python paints them with the Z80's own script interpreter, following the pointers at 0x79BC. That they are right is not an impression — the plan was compared cell by cell against thirty openMSX dumps, with zero differences
Hole 1 up close, par 4 and 475 metres. The left column is the whole panel as the cartridge builds it; the wind cell is blank because that figure is not in the ROM — the R register draws it
Hole 1 up close, par 4 and 475 metres. The left column is the whole panel as the cartridge builds it; the wind cell is blank because that figure is not in the ROM — the R register draws it
Hole 5, the course's only par 3: 168 metres, with the green almost on top of the tee and bunkers in between
Hole 5, the course's only par 3: 168 metres, with the green almost on top of the tee and bunkers in between
The title screen, built with the cartridge's own steps: the script at 0x44E9 decompresses the wordmark's 38 tiles and 0x4354 lays them out in two rows of 19 from VRAM 0x3887
The title screen, built with the cartridge's own steps: the script at 0x44E9 decompresses the wordmark's 38 tiles and 0x4354 lays them out in two rows of 19 from VRAM 0x3887
The 54 cell codes the perspective view is built from, each drawn at the <b>five distances</b> the cartridge stacks them at: the same code is four tiles tall up close and two at the back
The 54 cell codes the perspective view is built from, each drawn at the five distances the cartridge stacks them at: the same code is four tiles tall up close and two at the back
The swing's seven frames, decompressed from 0x78FF and assembled sprite by sprite the way 0x5862 does it, in their priority order
The swing's seven frames, decompressed from 0x78FF and assembled sprite by sprite the way 0x5862 does it, in their priority order
The course's 256 tiles with their colour, as they sit in VRAM once 0x482E has loaded the artwork
The course's 256 tiles with their colour, as they sit in VRAM once 0x482E has loaded the artwork
The 32 sprite patterns of 16×16 loaded during play: the golfer, the ball, its shadow and the flag
The 32 sprite patterns of 16×16 loaded during play: the golfer, the ball, its shadow and the flag
The font everything is written with. It is not ASCII: E0 is H, D3 is O, D9 is L and DC is E, and the digits are 0xF0 plus the digit
The font everything is written with. It is not ASCII: E0 is H, D3 is O, D9 is L and DC is E, and the digits are 0xF0 plus the digit