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
.outer_loop
asl mask
bcs .has_high_sprite
.back_o
dey
bne .outer_loop
rts
 
.has_high_sprite
beq .back_o ; no sprite to collide with
 
sty high_sprite ; remember sprite count
 
lda mask
.inner_loop
dey
asl
bcc .inner_loop
 
pha ; remember all remaining sprites
sty low_sprite
 
; do stuff
 
ldy low_sprite
pla
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: