After a bit of tweaking, I seem to have instructions timings just about correct... I'd hyper-corrected all memory accesses meaning there was an unnecessary wait state in a normal memory read which only seemed to show up in the 4-3-5 T-state pattern used in relative jumps, so those instructions were padded out to 1us longer than on a real CPC.
Additionally, I'd been struggling with the interrupt generation for a couple of days with it resolutely refusing to work right. I tracked that down to a typo and I was resetting the counter on the 2nd pixel in a hsync rather than the 2nd hsync in a vsync... Ooops!
So, compared to WinAPE, it's almost there now...
Tomorrow I'll try to get ROM banking working at which point I might be able to get BASIC booting. At the moment, there's a single "ROM" which is used for the boot image.