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.
Hey. Do you think you could provide some guidance with this:
ReplyDeletehttp://forums.sonicretro.org/index.php?showtopic=34181&pid=901848&st=75&#entry901848
Actually, I have zero experience with the PC port. I might end up having to take a look at it though, because there are so many things interesting with that screenshot. The checkerboard shadows! The color blending! The atrocious desync!
DeleteSomewhat related (but not really to Sonic 3), is the bad art pointer a similar reason to why placing an object while dead in Sonic 2 crashes the game?
ReplyDeleteThat's actually a different thing. When you die, the game stops processing object code so that every sprite other than Sonic freezes in place. Spawning new objects in this state clearly breaks something; Sonic 3 simply resumes processing objects as soon as you enter debug placement mode.
DeleteIsn't it actually supposed to "lock up" in the original Sonic 3 error handler, but re-init the game in SK/S3K?
ReplyDeleteI've experienced this too; isn't it _weird_ looking?
ReplyDelete