Actions

SCHG How-to

Extend the Sonic 1 sprite mappings and art limit

From Sonic Retro

(Original guide by MarkeyJester)

If you have worked with editing Sonic's sprites for Sonic 1 then you may have noticed a few issues in-game when Sonic has more than a certain number of sprites. By default, Sonic 1 has a sprite map limit of $7F sprites in one set, and several parts of the game engine will prevent you from having more.

In this guide we look at expanding that size to something more generous.

Extending Sonic's animation limit

First thing's first, in routine "Sonic_Animate:" you'll find the following code:

SAnim_Do2:
		moveq	#0,d1
		move.b	$1B(a0),d1	; load current frame number
		move.b	1(a1,d1.w),d0	; read sprite number from script
		bmi.s	SAnim_End_FF	; if animation is complete, branch

SAnim_Next:
		move.b	d0,$1A(a0)	; load sprite number
		addq.b	#1,$1B(a0)	; next frame number

Now, the instruction with "bmi" is what is responsible for preventing the animation scripts of Sonic reaching passed $7F, "bmi" is an instruction that will branch if the value is negative, a negative value is from $80 to $FF which is no good to us, replace the code above, with this below:

SAnim_Do2:
		moveq	#0,d1
		move.b	$1B(a0),d1	; load current frame number
		move.b	1(a1,d1.w),d0	; read sprite number from script
		cmp.b	#$FD,d0					; MJ: is it a flag from FD to FF?
		bhs	SAnim_End_FF				; MJ: if so, branch to flag routines

SAnim_Next:
		move.b	d0,$1A(a0)	; load sprite number
		addq.b	#1,$1B(a0)	; next frame number

This will ensure that values from $FD to $FF will branch to "SAnim_End_FF:" (where "bmi" used to branch to) and values from $00 to $FC will continue down to "SAnim_Next:", basically expaning the script reading from $00 - $7F to $00 - $FC.

(The reason we're not allowing it to go up to $FF is because values $FD, $FE and $FF are special flags for things like walking/running speed/rotation, etc).

Extending Sonic's map/sprite limit

The second thing that needs to be changed is the code that reads the mapping data offset in "BuildSprites:":

loc_D700:
		movea.l	4(a0),a1
		moveq	#0,d1
		btst	#5,d4
		bne.s	loc_D71C
		move.b	$1A(a0),d1
		add.b	d1,d1
		adda.w	(a1,d1.w),a1
		move.b	(a1)+,d1
		subq.b	#1,d1
		bmi.s	loc_D720

What happens here is the map ID is loaded into d1 "move.b $1A(a0),d1", and it is then multiplied by 2 "add.b d1,d1", but this only multiplies a "byte" of data, so map ID's higher than $7F won't multiply properly (e.g. $80 x $02 = $100, only the the right two digits are read $00). So to fix this is very simple, the .b in "add.b d1,d1", change to .w for word. This will ensure values from $80 to $FC will multiply by 2 properly.

Because we changed that value from byte to word, we have caused a potential (and fatal) error that will cause the game to crash if a sprite map ID from $80 to $FC is set to show. What's causing this is the sprite counter, the line "move.b (a1)+,d1" will load the "counter" (the "counter" is used to set how many sprites to process in one map list), but it's loading a byte of data, this isn't a problem as that's what we want it to do, but our new "add.w d1,d1" has changed a "word" of data in d1, so the left side of the word is still in d1 and needs to be cleared, adding "moveq #$00,d1" above "move.b (a1)+,d1" will ensure that the register is cleared and ready to use.

After the changes the routine should look like this:

loc_D700:
		movea.l	4(a0),a1
		moveq	#0,d1
		btst	#5,d4
		bne.s	loc_D71C
		move.b	$1A(a0),d1
		add.w	d1,d1					; MJ: changed from byte to word (we want more than 7F sprites)
		adda.w	(a1,d1.w),a1
		moveq	#$00,d1					; MJ: clear d1 (because of our byte to word change)
		move.b	(a1)+,d1
		subq.b	#1,d1
		bmi.s	loc_D720

Extending Sonic's art limit

OK, so the animation script reads up to $FC, the mappings can be read up to $FC too, however, having $FC sprites to use you'll no-doubt be using a lot of art, unfortunately Sonic 1 can only load art of Sonic's art offset from 0000 to FFFF (this is a maximum of $7FF tiles), we're going to expand it to 1FFFF (maximum of $FFF tiles).

In routine "LoadSonicDynPLC:" (For HG disassembly users, this is "Sonic_LoadGfx:". You want to find the label "@readentry")

SPLC_ReadEntry:
		moveq	#0,d2
		move.b	(a2)+,d2
		move.w	d2,d0
		lsr.b	#4,d0
		lsl.w	#8,d2
		move.b	(a2)+,d2
		lsl.w	#5,d2
		lea	(Art_Sonic).l,a1
		adda.l	d2,a1

This section will load data from Sonic's DPLC getting the number of tiles to load, and the tile ID, the tile ID is multiplied by $20 to get the offset of Sonic's art, but, only a word is read so the offset may only go up to $FFFF, let's fix this by changing the .w in "lsl.w #5,d2" to .l, this will ensure that it muleitplies the value into long-word rather than just word.

However, d2 still contains the "number of tiles to load" counter on the far left nybble, so let's clear that nybble by putting "andi.w #$0FFF,d2" above "lsl.l #5,d2".

The routine should look like this when it's done:

SPLC_ReadEntry:
		moveq	#0,d2
		move.b	(a2)+,d2
		move.w	d2,d0
		lsr.b	#4,d0
		lsl.w	#8,d2
		move.b	(a2)+,d2
		andi.w	#$0FFF,d2				; MJ: clear the counter
		lsl.l	#5,d2					; MJ: shifting long-word instead of word (more than FFFF bytes)
		lea	(Art_Sonic).l,a1
		adda.l	d2,a1

And that's it, that's all there is to it. You can now have Sonic sprites up to $FC, and have Sonic art the size of $1FFFF. Almost double the amount.

Extending animation limit (for all other objects)

This next change is optional, we have only extended Sonic's limit but we haven't extended the limit of all the other objects in the game, seeing as we've already changed the code in "BuildSprites" to allow more than $7F sprite mappings globally, we may as well allow objects that use the "AnimateSprite:" routine to use more than $7F too.

So, at routine "AnimateSprite:" you'll see:

Anim_Run:
		subq.b	#1,$1E(a0)	; subtract 1 from frame	duration
		bpl.s	Anim_Wait	; if time remains, branch
		add.w	d0,d0
		adda.w	(a1,d0.w),a1	; jump to appropriate animation	script
		move.b	(a1),$1E(a0)	; load frame duration
		moveq	#0,d1
		move.b	$1B(a0),d1	; load current frame number
		move.b	1(a1,d1.w),d0	; read sprite number from script
		bmi.s	Anim_End_FF	; if animation is complete, branch

Anim_Next:

Just like we did in "Sonic_Animate:", we need to change that "bmi" code in the same way, for the very same reasons, there is only one difference however, we can only have the limit up to $F9 for objects as they have additional flags ($FA, $FB and $FC) for altering the routine counter, but it's still better than $7F, so let's change it:

Anim_Run:
		subq.b	#1,$1E(a0)	; subtract 1 from frame	duration
		bpl.s	Anim_Wait	; if time remains, branch
		add.w	d0,d0
		adda.w	(a1,d0.w),a1	; jump to appropriate animation	script
		move.b	(a1),$1E(a0)	; load frame duration
		moveq	#0,d1
		move.b	$1B(a0),d1	; load current frame number
		move.b	1(a1,d1.w),d0	; read sprite number from script
		cmp.b	#$FA,d0					; MJ: is it a flag from FA to FF?
		bhs	Anim_End_FF				; MJ: if so, branch to flag routines

Anim_Next:

As you can see, if the value is from $00 to $F9, it'll continue down to "Anim_Next:", whilst if the value is from $FA to $FF, it'll branch to "Anim_End_FF" to run through the flag routines.

And there we have it, now objects can animate sprite mappings up to $79, have fun with the extended space!

SCHG How-To Guide: Sonic the Hedgehog (16-bit)
Fixing Bugs
Fix Demo Playback | Fix a Race Condition with Pattern Load Cues | Fix the SEGA Sound | Display the Press Start Button Text | Fix the Level Select Menu | Fix the Hidden Points Bug | Fix Accidental Deletion of Scattered Rings | Fix Ring Timers | Fix the Walk-Jump Bug | Correct Drowning Bugs | Fix the Death Boundary Bug | Fix the Camera Follow Bug | Fix Song Restoration Bugs | Fix the HUD Blinking | Fix the Level Select Graphics Bug | Fix a remember sprite related bug
Changing Design Choices
Change Spike Behavior | Collide with Water After Being Hurt | Fix Special Stage Jumping Physics | Improve the Fade In\Fade Out Progression Routines | Fix Scattered Rings' Underwater Physics | Remove the Speed Cap | Port the REV01 Background Effects | Port Sonic 2's Level Art Loader | Retain Rings Between Acts | Add Sonic 2 (Simon Wai Prototype) Level Select | Improve ObjectMove Subroutines | Port Sonic 2 Level Select
Adding Features
Add Spin Dash ( Part 1 / Part 2 / Part 3 / Part 4 ) | Add Eggman Monitor | Add Super Sonic | Add the Air Roll
Sound Features
Expand the Sound Index | Play Different Songs Per Act | Port Sonic 2 Final Sound Driver | Port Sonic 3's Sound Driver | Port Flamewing's Sonic 3 & Knuckles Sound Driver | Change The SEGA Sound
Extending the Game
Load Chunks From ROM | Add Extra Characters | Make an Alternative Title Screen | Use Dynamic Tilesets | Make GHZ Load Alternate Art | Make Ending Load Alternate Art | Add a New Zone | Set Up the Goggle Monitor | Add New Moves | Add a Dynamic Collision System | Dynamic Special Stage Walls System | Extend Sprite Mappings and Art Limit | Enigma Credits | Use Dynamic Palettes
Miscellaneous
Convert the Hivebrain 2005 Disassembly to ASM68K
Split Disassembly Guides
Set Up a Split Disassembly | Basic Level Editing | Basic Art Editing | Basic ASM Editing (Spin Dash)

|Extend the Sonic 1 sprite mappings and art limit]]