Here's the relevant code from the crumbling platform object:
loc_205A6: move.b $2A(a0),d0 andi.b #$18,d0 beq.s loc_205B6 move.b #1,$3A(a0) loc_205B6: moveq #0,d1 move.b 7(a0),d1 movea.l $3C(a0),a2 move.w $10(a0),d4 jsr (SolidObjectTopSloped2).l bra.w Sprite_OnScreen_TestOkay, so. At loc_205A6, we're testing the status of two bits in the object's status bitfield at $2A(a0). These bits are set depending on whether player 1 and/or player 2 are standing on the object. If either player is on the platform, $3A(a0) is set to 1, which signals the platform to be destroyed and eventually results in the object switching over to this code:
subq.b #1,$38(a0) bne.s locret_2061E move.l #loc_20620,(a0) lea (Player_1).w,a1 moveq #3,d6 bsr.s sub_205FC lea (Player_2).w,a1 moveq #4,d6 sub_205FC: btst d6,$2A(a0) beq.s locret_2061E bclr d6,$2A(a0) bclr #3,$2A(a1) bclr #5,$2A(a1) bset #1,$2A(a1) move.b #1,$21(a1) locret_2061E: rtsOnce per frame, $38(a0) is decremented from a starting value of 7. When it reaches 0, the object calls the sub_205FC function twice, once for each player. This function checks whether a given player is currently on the platform (by testing the status bit specified in d6) and if so, forces them off. Since the function's body begins right after the code which calls it, the second bsr.s instruction is elided and the code falls directly into the function.
Note that in the case where $38(a0) isn't zero, we haven't actually run the code that makes the platform solid, as we do at loc_205B6. We can't do it at the end of this block due to the aforementioned fallthrough, but we have to do it before locret_2061E, and we can't do it between the decrement and the branch, since it would mess with the latter's result. So they ended up doing it in the absolute worst possible place:
loc_205A6: move.b $2A(a0),d0 andi.b #$18,d0 beq.s sub_205B6 move.b #1,$3A(a0) sub_205B6: moveq #0,d1 move.b 7(a0),d1 movea.l $3C(a0),a2 move.w $10(a0),d4 jsr (SolidObjectTopSloped2).l bra.w Sprite_OnScreen_Test ; --------------------------------------------------------------------------- ... loc_205DE: bsr.w sub_205B6 subq.b #1,$38(a0) bne.s locret_2061E ...Oh dear. There are now 8 frames in which if the platform is scrolled away, sub_205B6 will call Sprite_OnScreen_Test, which will then call Delete_Current_Sprite, which will set every byte on the object's SST to 0. And then, like some sort of shambling corpse, the object will execute the rest of loc_205DE, decrementing $38(a0) once before exiting.
We now have a free slot in which byte $38 is not zero, ready to screw over whatever object happens to spawn there.
tst.b $38(a0) bne.s loc_2C9CA jsr (Create_New_Sprite).l bne.s loc_2C9C4 move.l #Obj_EnemyScore,(a1) move.w $10(a0),$10(a1) move.w $14(a0),$14(a1) move.w $3E(a0),d0 lsr.w #1,d0 move.b d0,$22(a1) loc_2C9C4: jmp (Draw_Sprite).l ; --------------------------------------------------------------------------- loc_2C9CA: move.b #$1C,5(a0) clr.w $18(a0) jmp (Draw_Sprite).lThrough spectacular coincidence, the init code for the animal object checks the value of $38(a0), and if it isn't zero, the routine byte is set to $1C, and as can be seen in the video above, no score popup object is created.
But what is routine $1C? And under which circumstances is byte $38 set prior to the object's own initialization?
loc_2CB02: tst.b 4(a0) bpl.w loc_2C9DA subq.w #1,$36(a0) bne.w loc_2CB1E move.b #2,5(a0) move.w #$80,8(a0) loc_2CB1E: jmp (Draw_Sprite).lTurns out, routine $1C is actually leftover code from Sonic 2. In Sonic 2, animals inside end capsules are spawned with $38(a0) set to 1, and $36(a0) set in increments of 8. This causes them to all enter loc_2CB02 and begin decrementing $36(a0), and as they finish, they return to their normal routines and hop away one by one.
Unfortunately, our animal spawned with $36(a0) already set to 0, which after decrementing becomes $FFFF, or 65,535. In other words, it will wait 65,535 frames before hopping away. That's over 18 minutes. There's no saving him.
So please make sure you don't do anything after calling Delete_Current_Sprite. Think of the animals.
I have made a video showcasing that, using debug mode, waiting about 18 minutes will eventually cause the animal to fly away.
ReplyDeletehttps://youtu.be/pf5fHMK0u34
That is amazing.
Delete