looks like the bug is caused by the complicated irq handling. I'd remove all the logics which differentiates vblank and makes sure it happens. instead simply fire the vblank irq at a position where it will surely not interfere with a sprite irq (bottom border+some lines).
ldy SpriteHWAssign.w,X lda SpriteSortedFlags.w,X sta $d027,Y ;Color lda SpriteSortedTileNmr.w,X sta $7f8,Y ;Tile number tya asl A tay inc $d020 lda SpriteSortedPosY.w,X sta $d001,Y lda SpritePosXL.w,X sta $d000,Y lda D017Buffer.w,X sta $d017 lda D01dBuffer.w,X sta $d01d lda D01bBuffer.w,X ;Copy all flags sta $d01b lda D01cBuffer.w,X sta $d01c lda D015Buffer.w,X sta $d015 lda D010Buffer.w,X sta $d010 dec $d020 - inx cpx #SpriteAmount ;Last sprite ? bcs _lastInt lda InterruptLine.w,X beq - ;If raster line is 0, we should ignore it cmp #$ff beq _lastInt ;If value is $ff, then it's the last interrupt of the frame stx SpriteRasterIndex ;Copy valid index and rasterline sta $d012 sec sbc #$02 ;If a new interrupt should occur in less than 2 scanlines cmp $d012 ;Do it straight away bcc SpriteIRQ bcs + _lastInt lda #$ff sta $d012 sta SpriteRasterIndex + pla tay pla tax pla rti
Acknowledging can be done anywhere in the IRQ, but you might kill some raster IRQs if you do it late. The routine you show up there already eats ~3 rasterlines alone, so if you have "an IRQ the next rasterline" and acknowledge it at the end of that IRQ, the next-rasterline IRQ will never happen. If you do it early, the IRQ will happen but atleast 2 rasterlines too late due to RTI being so late.
IRQ inc $d019 ;Acknownledge the raster IRQ pha txa pha tya pha jsr SpriteMultiplexer jsr InitSprites jsr SetSprites jsr MoveStars jsr MoveCenter lda FrameCtr eor #$01 sta FrameCtr pla tay pla tax pla NMI rti SpriteIRQ inc $d019 sta SpriteStoreA stx SpriteStoreX sty SpriteStoreY ldx SpriteRasterIndex _loop ldy SpriteHWAssign.w,X lda SpriteSortedFlags.w,X sta $d027,Y ;Color lda SpriteSortedTileNmr.w,X sta $7f8,Y ;Tile number tya asl A tay lda D017Buffer.w,X sta $d017 lda D01dBuffer.w,X sta $d01d lda D01bBuffer.w,X ;Copy all flags sta $d01b lda D01cBuffer.w,X sta $d01c lda D015Buffer.w,X sta $d015 lda D010Buffer.w,X sta $d010 lda SpritePosXL.w,X sta $d000,Y lda SpriteSortedPosY.w,X sta $d001,Y - inx lda InterruptLine,X beq - ;If raster line is 0, we should ignore it sta $d012 cmp #$ff beq _lastInt ;If value is $ff, then it's the last interrupt of the frame stx SpriteRasterIndex ;Copy valid index and rasterline sec sbc #$03 ;If a new interrupt should occur in less than 2 scanlines cmp $d012 ;Do it straight away bcc _loop bcs + _lastInt lda #<IRQ sta $fffe lda #>IRQ sta $ffff + lda SpriteStoreA ldx SpriteStoreX ldy SpriteStoreY rti
sta areg+1 stx xreg+1 xty yreg+1 ... areg: lda #$00 xreg: ldx #$00 yreg: ldy #$00 rti