Dizzy 7 on ZX Spectrum - incorrect interrupt mode 2 initialization
Dizzy 7 (the original 1992 release) has a game-breaking bug, which is fortunately mitigated by a technicality of the ZX Spectrum itself.

In short, if the data bus contains a byte other than 0xFF when the CPU is looking up the address of the IM2 interrupt handler, undefined behaviour will occur - including computer resets.

I have discovered this while I was trying to make Dizzy 7 run in my emulator, zxian.

A little background

Games on the ZX Spectrum's generally rely on the Z80 CPU's interrupt mode 2 (IM2) for various purposes.

Upon initialization, games write a pre-determined value into the Z80's I register. This value will be used as the MSB of a 128-entry IM2 handler lookup table.

The LSB of the entry is read from the data bus. The issue here is that it is difficult to guarantee what the bus configuration looks like at IM2 handler lookup time. Certain peripherals can affect the bus.

Therefore, ALL 128 entries of the handler lookup table are - in theory - equally as likely to be chosen by the CPU. Not only this, but since the LSB can even take on value 0xFF, a 257th byte must be supplied at the end of the 128 entries (of 2 bytes each) for "safety".

Thus, the common pattern of a table of 257 duplicated bytes has arisen in the ZX Spectrum development world.

A typical setup

On initialization, the game disables interrupts.

Then, it sets I=0xFE. This is essentially saying that the handler lookup table will start at 0xFE00.

257 bytes between 0xFE00 and 0xFF00 inclusive contain the value 0xFD, like so:
0xFE00: 0xFD
0xFE01: 0xFD
0xFE02: 0xFD
0xFEFF: 0xFD
0xFF00: 0xFD

The start of the handler begins at 0xFDFD like so:
0xFDFD: JP [actual start of handler]

Thus, when an IM2 occurs,

MSB = I register = 0xFE

LSB = theoretically random data bus byte 0xKK

Then, the address of the handler is the 2-byte value at 0xFEKK. Due to how the 257-byte table was setup with all 0xFD bytes, the address of the handler will always be 0xFDFD - irrespective of the theoretically random data bus byte.

How Dizzy 7 got lucky In the vast majority of cases (barring certain peripherals being present), the data bus byte on a ZX Spectrum is 0xFF at the start of an IM2 interrup.

At least one ZX Spectrum reference book mentions this, and four open source emulators I've inspected make this assumption, implementing IM2 handler lookup by hardcoding the data bus byte to 0xFF, as such:

handler entry = (I << 8) | 0xFF

Dizzy 7 did two things wrong

  • Either
    • its 257-byte array is not 0x100-aligned, if you count it as starting at 0xFDFF ("inside" the JP 0xFDF1 instruction) and ending at 0xFEFF, OR
    • they only allocated 256 bytes, if you count it as starting at 0xFE00 and ending at 0xFEFF
  • At 0x8209 it sets I=0xFD incorrectly, instead of the correct 0xFE. This incorrect value remains unchanged from this point on

What saved it on real hardware?

On real hardware, Dizzy 7 is saved by the fact that the data bus byte is frequently 0xFF during IM2. So MSB=I register=0xFD and LSB=0xFF (from bus), so the handler address is most likely found at address 0xFDFF.

By chance, the last byte of the instruction at 0xFDFD is in fact 0xFD.
0xFDFD: 0xC3   ; these 
0xFDFE: 0xF1   ; three bytes are
0xFDFF: 0xFD   ;  JP 0xFDF1

; table starts below

0xFE00: 0xFD
0xFE01: 0xFD
Thus, the handler address read from the value at 0xFDFF is 0xFDFD.

What saved it on emulators?

On emulators, Dizzy 7 is saved by the fact that emulator authors hardcode 0xFF as the LSB during IM2 handler lookup.

Other games

I've verified Dizzy 1 through 6 and they all set up IM2 handler lookup tables and I register adequately.