Phoenix's Scoring Bug Analyzed and Fixed
By Don Hodges
Posted 1/22/2010. Updated 8/14/2015
The 1980 arcade game, Phoenix, was created by Amstar Electronics. It is fairly well known that if the player shoots 3 birds in a row very quickly as they fly upwards during the second stage of the game, the score will jump to around 204,000 points, no matter what the player's current score is.
A video of the feat can be seen here: http://www.youtube.com/watch?v=OABVZGUMMkY
After some digging around in the assembly language code and using the MAME emulator with the dubugger, we can discover the reason that this bug exists, and design a fix for it.
The program reserves 4 slots of memory for birds that have been hit by the player's missile. Each of these slots are 4 bytes long, and they are all contained in a single area of memory at locations #4370 through #437F. [The # indicates a hexadecimal number.] Slots 1 and 2 are normally used for the regular death animation for most of the birds. Slots 3 and 4 are used for the special death animation which has the score shown in the middle of two shells which open on both sides of it and move apart. This is used for small birds that are flying upwards with wings outstretched, and for the large birds in the third and fourth stages.
Table 1: Slots for bird death animations.
In each slot, the first byte is used as a counter which indicates the state of the animation of the dying bird. When a bird is hit, this byte is normally set to #C, or is set to #10 for the special animation. This counter is decreased as the animation of the dying bird goes through its stages, and reaches 0 (zero) when it is complete.
When a bird is hit, the program first checks to see which type of bird was hit. If it was a normal bird, the memory location that is checked first is set to #4370, which is the first byte of the first slot. If it is one that uses the special animation, the first memory checked is set to #4378, which is the first byte of the third slot.
Then it examines the first byte from either slot 1 or slot 3. If the byte is zero, this means that the slot is open to use and the code then uses this slot to store 4 bytes of data that are related to the animation of the dying bird.
If the byte is not zero, this means that the slot is being used and the next slot is then checked. The program then checks either slot 2 or 4. If the first byte is zero, then it is then used, but if not then the code uses the next slot, without doing any more checks.
This works out OK for regular hits. The third death animation becomes the special one that pulls apart onscreen.
However if 3 birds flying upward are hit in quick succession, then the program, after checking the third slot and the fourth slot, ends up using 4 bytes starting at #4380 to store the data, in a nonexistent fifth slot. The memory locations #4381, #4382, and #4383 are normally used to hold the player's score! [The first byte #4380, does not appear to be used for anything.] These bytes get overwritten. #4381 always gets the value #20. This corresponds to the player's hundred thousands and ten thousands digits. The second byte #4382 gets a value of #41 or #42. This corresponds to the player's thousands and hundreds digits. The last byte can vary widely, and represents the player's tens and ones digits. When this occurs, the player's score becomes #204,XYZ, where X can be 1 or 2 and Y and Z can be various digits. [The score is held in memory in Binary Coded Decimal].
The assembly language code with my comments for all of this follows, for those that are interested. You may need some assembly language knowledge to follow this. The CPU code is assembly language from the Intel 8085A.
; come here when a bird has been hit. 0EB8: 21 78 43 lxi h,$4378 ; HL := #4378 [slot for special animation] 0EBB: 7A mov a,d ; A := D 0EBC: FE 10 cpi $10 ; Are we to use the special animation? 0EBE: CA C3 0E jz $0ec3 ; Yes, skip next step 0EC1: 2E 70 mvi l,$70 ; Else HL := #4370 [slot for regular animation] 0EC3: 7E mov a,m ; Load timer from this slot 0EC4: A7 ana a ; Is this slot available? 0EC5: CA D5 0E jz $0ed5 ; Yes, skip ahead, we will use this slot ; else check next slot ... 0EC8: 2C inr l 0EC9: 2C inr l 0ECA: 2C inr l 0ECB: 2C inr l ; Increase HL by 4. [now at 2nd or 4th slot] 0ECC: 7E mov a,m ; Load timer from this slot 0ECD: A7 ana a ; Is this slot available? 0ECE: CA D5 0E jz $0ed5 ; yes, skip ahead, we will use this slot ; else use the next slot. Bugged when birds are flying upwards. ; source of 204K bug. HL becomes #4380 which is start of score. 0ED1: 2C inr l 0ED2: 2C inr l 0ED3: 2C inr l 0ED4: 2C inr l ; Increase HL by 4 [now at 3rd or 5th slot] ; there should have been a check here to see if HL==#4380 and change it if so. 0ED5: 72 mov m,d ; Store D into byte 1 0ED6: 2C inr l ; Next byte 0ED7: 73 mov m,e ; Store E into byte 2. score becomes 20xxxx 0ED8: 2C inr l ; Next byte 0ED9: 70 mov m,b ; Store B into byte 3. score becomes 2041xx or 2042xx 0EDA: ...
We can fix the bug by checking the value of L after it finds the first 2 slots filled and gets ready to use the next slot. If L is #80, then we will set it to #70 so that the first slot is used instead of the score RAM. This will end up using a normal animation for the bird death instead of the special animation, but this is probably the best way to fix this without doing a much more complete code rewrite. The fix does not appear possible to patch within the confines of the original codespace, so a hook is created to a new subroutine in an area of unused memory.
This bug can be fixed by the following patch, which requires changing 10 bytes of code:
0ED2: CD E6 0E call $0EE6 ; call patch ... 0EE6: 2C inr l ; restore instructions wiped by the patch call 0EE7: 2C inr l ; 0EE8: 2C inr l ; if L becomes #80 then the S flag is set. Did it happen? 0EE9: F0 rp ; no, return and continue normally 0EEA: 2E 70 mov l,$70 ; else set L to #70 to use 1st slot, not the score RAM 0EEC: C9 ret ; return
This patch has been tested and does work. It can be implemented by a MAME cheat code:
:phoenix:20600000:0EEA:002E70C9:00FFFFFF:Fix 204K bug :phoenix:20710000:0EE6:2C2C2CF0:FFFFFFFF:Fix 204K bug (2/3) :phoenix:20610000:0ED2:00CDE60E:00FFFFFF:Fix 204K bug (2/3)
Learn more about MAME cheats at http://cheat.retrogames.com
Comments and Conclusions:
Some have suggested that this bug is actually an "Easter Egg", but I disagree. It seems like this bug was not intentional. In my opinion it looks like some sloppy programming. It would suck to be going for a record score and accidentally trigger this and set your score BACKWARDS.
Much thanks to Steve Fewell for inspiring me to look into this bug and providing help, and feedback on drafts of this page.
|In accordance with Title 17 U.S.C. Section 107, some of the material on this site is distributed without profit to those who have expressed a prior interest in receiving the included information for research and educational purposes. For more information go to: http://www.law.cornell.edu/uscode/17/107.shtml. If you wish to use copyrighted material from this site for purposes of your own that go beyond 'fair use', you must obtain permission from the copyright owner.|