New to Sonic 3 are various different kinds of objects which take over Sonic's movement and rotate him using a series of 3D rotation sprites. Rather than do this through animations, these objects modify Sonic's mapping frame directly so he's always in the correct pose, depending on his distance to the center of the object.
Should you enter debug mode while riding one of these objects however, everything catches on fire pretty quickly, with some emulators outputting garbage sprites to the screen, where as other emulators, as well as hardware, mysteriously reset back to the SEGA screen.
So what's going on? Well, right off the bat, when you enter object placement mode, the first thing that happens is that the player character's mappings and art pointer are backed up to somewhere else in RAM.
DebugMode:
moveq #0,d0
move.b (Debug_placement_mode).w,d0
move.w off_92A1C(pc,d0.w),d1
jmp off_92A1C(pc,d1.w)
; ---------------------------------------------------------------------------
off_92A1C: dc.w loc_92A20-off_92A1C
dc.w loc_92AB0-off_92A1C
; ---------------------------------------------------------------------------
loc_92A20:
addq.b #2,(Debug_placement_mode).w
move.l $C(a0),($FFFFFFCA).w ; save mappings to $FFCA
cmpi.b #6,5(a0)
bhs.s loc_92A38
move.w $A(a0),($FFFFFFCE).w ; save art tile to $FFCE
loc_92A38:
...
The reason for this is that, in order to display the preview sprite for the currently selected object, the player's mappings and art are temporarily replaced with those of the target object. The player's mapping frame is also changed in order to pick the appropriate sprite from the sprite mapping definitions.
If this isn't setting off alarm bells already, it should. Remember how the 3D rotation objects would rather mess with the player's mapping frame directly? Well, the Carnival Night Zone barrel above, as an example, animates Sonic by setting his mapping frame to a value between $55 and $5B. These correspond to the standing rotation sprites as seen below.
Ordinarily, what happens is that a mapping frame of $55 gets multiplied by two, and the result, $AA, is added to Sonic's base mappings address in order to retrieve the pointer to the actual mapping definition for mapping frame $55.
However, when we enter object placement mode, Sonic's mappings pointer is replaced with the pointer for the currently selected object, for instance, a ring. Now, a ring has
nine whole frames of animation, so when we add $AA to the base mappings address for rings at $1A99A, we end up with the value $1AA44, which is well past all the mapping definitions and their pointers, and smack dab in the middle of the code for the rings from the slot machine bonus cage.
Map_Ring: dc.w word_1A9AC-Map_Ring ; 1A99A: 0012
dc.w word_1A9B4-Map_Ring ; 1A99C: 001A
dc.w word_1A9BC-Map_Ring ; 1A99E: 0022
dc.w word_1A9C4-Map_Ring ; 1A9A0: 002A
dc.w word_1A9CC-Map_Ring ; 1A9A2: 0032
dc.w word_1A9D4-Map_Ring ; 1A9A4: 003A
dc.w word_1A9DC-Map_Ring ; 1A9A6: 0042
dc.w word_1A9E4-Map_Ring ; 1A9A8: 004A
dc.w word_1A9EC-Map_Ring ; 1A9AA: 0052
word_1A9AC: dc.w 1
dc.b $F8, 5, 0, 0, $FF, $F8
word_1A9B4: dc.w 1
dc.b $F8, 5, 0, 4, $FF, $F8
word_1A9BC: dc.w 1
dc.b $F8, 1, 0, 8, $FF, $FC
word_1A9C4: dc.w 1
dc.b $F8, 5, 8, 4, $FF, $F8
word_1A9CC: dc.w 1
dc.b $F8, 5, 0, $A, $FF, $F8
word_1A9D4: dc.w 1
dc.b $F8, 5, $18, $A, $FF, $F8
word_1A9DC: dc.w 1
dc.b $F8, 5, 8, $A, $FF, $F8
word_1A9E4: dc.w 1
dc.b $F8, 5, $10, $A, $FF, $F8
word_1A9EC: dc.w 0
; ---------------------------------------------------------------------------
loc_1A9EE:
moveq #0,d0 ; 1A9EE: 7000
move.b 5(a0),d0 ; 1A9F0: 1028 0005
move.w off_1A9FC(pc,d0.w),d1 ; 1A9F4: 323B 0006
jmp off_1A9FC(pc,d1.w) ; 1A9F8: 4EFB 1002
; ---------------------------------------------------------------------------
off_1A9FC: dc.w loc_1AA02-off_1A9FC ; 1A9FC: 0006
dc.w loc_1AA56-off_1A9FC ; 1A9FE: 005A
dc.w loc_1AA62-off_1A9FC ; 1AA00: 0066
; ---------------------------------------------------------------------------
loc_1AA02:
moveq #0,d1 ; 1AA02: 7200
move.w $3C(a0),d1 ; 1AA04: 3228 003C
swap d1 ; 1AA08: 4841
move.l $34(a0),d0 ; 1AA0A: 2028 0034
sub.l d1,d0 ; 1AA0E: 9081
asr.l #4,d0 ; 1AA10: E880
sub.l d0,$34(a0) ; 1AA12: 91A8 0034
move.w $34(a0),$10(a0) ; 1AA16: 3168 0034 0010
moveq #0,d1 ; 1AA1C: 7200
move.w $3E(a0),d1 ; 1AA1E: 3228 003E
swap d1 ; 1AA22: 4841
move.l $38(a0),d0 ; 1AA24: 2028 0038
sub.l d1,d0 ; 1AA28: 9081
asr.l #4,d0 ; 1AA2A: E880
sub.l d0,$38(a0) ; 1AA2C: 91A8 0038
move.w $38(a0),$14(a0) ; 1AA30: 3168 0038 0014
lea Ani_Ring(pc),a1 ; 1AA36: 43FA 002E
bsr.w Animate_Sprite ; 1AA3A: 6100 01AC
subq.w #1,$40(a0) ; 1AA3E: 5368 0040
bne.w Draw_Sprite ; 1AA42: 6600 0182 <---------
movea.l $2E(a0),a1 ; 1AA46: 2268 002E
subq.w #1,(a1) ; 1AA4A: 5351
bsr.w GiveRing ; 1AA4C: 6100 FB48
...
We're now trying to read garbage information as mappings data, which explains the digital vomit seen in emulators, but in reality, it's even worse. The word value at $1AA44 is 0001 which, when added to $1A99A, results in $1A99B, an odd address. Odd not as in peculiar, but indivisible by 2.
loc_1ADB2:
...
movea.l mappings(a0),a1
moveq #0,d4
btst #5,d6 ; is the static mappings flag set?
bne.s loc_1ADD8 ; if it is, branch
move.b mapping_frame(a0),d4
add.w d4,d4
adda.w (a1,d4.w),a1
move.w (a1)+,d4 ; <--------- <--------- <---------
subq.w #1,d4 ; get number of pieces
...
The Render_Sprites function proceeds to read the sprite piece count from address $1A99B. This results in an attempt to read a word value from an odd address, which is an illegal operation on the 68000 processor. An exception is raised, which is caught by Sonic 3's exception handler, and the game resets in response to the unexpected catastrophe.
Emulators which ignore this hardware rule in favor of increased performance don't raise this exception, so the garbage information is successfully read, resulting in garbage sprites being displayed on screen.