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 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.
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 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:
cp 021h)cp 020h)0x683D adds both scores, runs daa and checks whether the units digit is 0 or 5The 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.
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.
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.
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.
0x4764 header yes, paints0x476C header no, everything ready0x475C header no, paints0x4760 header yes, ERASESThe fourth is the clever one: with the mask at zero, the very script that wrote the label erases it, keeping its shape.
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.
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.
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.
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.
Under each picture is where it comes from and what is on it.