Tuesday, July 24, 2007

The mother of all bugs killed

It seems that I finally nailed it! Whenever the collision check had to swap sprite indexes (to avoid having separate routines for droid-laser and laser-droid collision, for example) it also swapped counters for outer/inner loops.

    lda $d015       ; active sprites
and #$7e ; mask out player and player_fire
sta mask
ldy #7
asl mask
bcs .has_high_sprite
bne .outer_loop
beq .back_o ; no sprite to collide with
sty high_sprite ; remember sprite count
lda mask
bcc .inner_loop
pha ; remember all remaining sprites
sty low_sprite
; do stuff
ldy low_sprite
bne .inner_loop
ldy high_sprite
jmp .back_o

That one has no chance of working if low_sprite and high_sprite get swapped. Inner loop starts accessing too high sprites, and outer loops messes with lower sprites than intended, eventually accessing sprite 0 (player) and negative numbered sprites (completely random data).

This shows a situation where HLL compilers beat humans when generating code. Human with limited memory can't remember which routines read/write which variables, leading to local variables having way too long life span. Compilers know exactly which locations are needed and when, and that allows them to reuse local variable area much more efficiently.

BTW, that's my only admission to the mantra compiler writers repeat: "modern compilers write as good code as any human". I have seen some pretty impressive compilers for embedded systems, but I have seen them beaten, too ;)

No comments: