One of the main problems with the code was that it wasn't rasterising nicely and sprites were ending up with duplicated pixels at the top and left edges, and a missing row of pixels on the bottom and right edges. Everyone needs a test card for things like this, so here's mine:
And after some tinkering with the sprite code and associated fx file, I've finally got something that lines up nicely and gives nice 1:1 pixel mappings. The bottom row shows it with point texture sampling, the top with linear texture sampling, and as you can see it still stays nice and sharp. (double sized for clarity)
Wasn't as tough to fix as I had first though - mostly it was just adding a half-pixel translation to all drawing to make the texel centers line up with pixel centers, but there were also some inaccuracies in the texture coord generation in the code I pinched which were throwing things off.
Actual gameplay code has been progressing nicely, and now looks like this:
I realised after I'd done my character movement that actually Bubble Bobble has tiles half the size of the character, rather than the same size. Fortunately this wasn't as tough to change as I had thought since most of the code just uses relative offsets and traces the movement of individual points rather than moving from tile to tile explicitly.
Bubbles are proving awkward at the moment - they've got to sample the tile map in at least the four corners to check for blocks, which is easily doable but would introduce a large amount of duplicated code with the character movement, so I've got to figure out a way of performing generalised collision queries on a tile map.