Dynamic Special Stage Walls system
From Sonic Retro
(Original guide by Mercury)
This guide allows you to have dynamically loaded wall art in the Special Stages. Visually, this makes little to no difference when playing the Special Stage, but it will free VRAM, leaving A LOT of room for other things, like the HUD, or new blocks/objects! This guide follows the current HG disassembly version of Sonic 1.
Part 1: Fixing Wall Sprites
So the very first thing we will do, aside from backing up our disassembly, is decompress the Special Stage wall art. Now, you could simply decompress the art, and that's that, right? Wrong! Because we are using uncompressed animated art, we will be changing how the art is mapped together. The absolute easiest way to do this is to have all of the wall sprites under ONE single mapping frame. So, that means that for every possible wall frame, we will want 16 tiles. This requires some slight editing of the wall art to make every possible wall frame centered within a 32x32 pixel image. This shouldn't be too hard to do, as it really only requires fixing up the tiles for the first frame. A visual reference will be provided in the near future, to aid you. After this is done, you will want to save this art uncompressed, and relocate it to the artunc folder. With that finished, we want the asm pointing to the new art location. In sonic.asm, search for the following pointer:
Nem_SSWalls: incbin "artnem\Special Walls.bin" ; special stage walls
even
and replace it with this:
Art_SSWalls: incbin "artunc\Special Walls.bin" ; special stage walls
even
You don't need to change the pointer to "Art_SSWalls", but if you do, just know that you will need to perform a "Find & Replace" to correct any possible errors.
Now, we are going to change the mappings for the wall art. They currently look something like this:
; ---------------------------------------------------------------------------
; Sprite mappings - walls of the special stage
; ---------------------------------------------------------------------------
Map_SSWalls: dc.w byte_2C584-Map_SSWalls, byte_2C58A-Map_SSWalls
dc.w byte_2C590-Map_SSWalls, byte_2C596-Map_SSWalls
dc.w byte_2C59C-Map_SSWalls, byte_2C5A2-Map_SSWalls
dc.w byte_2C5A8-Map_SSWalls, byte_2C5AE-Map_SSWalls
dc.w byte_2C5B4-Map_SSWalls, byte_2C5BA-Map_SSWalls
dc.w byte_2C5C0-Map_SSWalls, byte_2C5C6-Map_SSWalls
dc.w byte_2C5CC-Map_SSWalls, byte_2C5D2-Map_SSWalls
dc.w byte_2C5D8-Map_SSWalls, byte_2C5DE-Map_SSWalls
byte_2C584: dc.b 1
dc.b $F4, $A, 0, 0, $F4
byte_2C58A: dc.b 1
dc.b $F0, $F, 0, 9, $F0
byte_2C590: dc.b 1
dc.b $F0, $F, 0, $19, $F0
byte_2C596: dc.b 1
dc.b $F0, $F, 0, $29, $F0
byte_2C59C: dc.b 1
dc.b $F0, $F, 0, $39, $F0
byte_2C5A2: dc.b 1
dc.b $F0, $F, 0, $49, $F0
byte_2C5A8: dc.b 1
dc.b $F0, $F, 0, $59, $F0
byte_2C5AE: dc.b 1
dc.b $F0, $F, 0, $69, $F0
byte_2C5B4: dc.b 1
dc.b $F0, $F, 0, $79, $F0
byte_2C5BA: dc.b 1
dc.b $F0, $F, 0, $89, $F0
byte_2C5C0: dc.b 1
dc.b $F0, $F, 0, $99, $F0
byte_2C5C6: dc.b 1
dc.b $F0, $F, 0, $A9, $F0
byte_2C5CC: dc.b 1
dc.b $F0, $F, 0, $B9, $F0
byte_2C5D2: dc.b 1
dc.b $F0, $F, 0, $C9, $F0
byte_2C5D8: dc.b 1
dc.b $F0, $F, 0, $D9, $F0
byte_2C5DE: dc.b 1
dc.b $F0, $F, 0, $E9, $F0
even
These are mappings for 16 frames. All of them but the first one are 4 tiles x 4 tiles, with the first frame being a 3x3 frame. This requires 249 ($F9) tiles loaded into VRAM to work. What we are going to do is consolidate this down to one single mapping. It will look like this:
; ---------------------------------------------------------------------------
; Sprite mappings - walls of the special stage
; ---------------------------------------------------------------------------
Map_SSWalls: dc.w byte_2C584-Map_SSWalls
byte_2C584: dc.b 1
dc.b $F0, $F, 0, 0, $F0
even
Now every frame can be created with one single mapping. Each one will be a 4x4 sprite, and will only require 16 tiles to be loaded into VRAM to make it happen. As the frame change is needed, the game simply needs to change the 16 tiles that are loaded into VRAM, and because EVERY wall tile will always be on the same frame at the same time, that's that! Now let's get the game to do just that!
Part 2: Code Tweaking
Next, we need to make some tweaks to the code. Go to VBla_0A: and find @nochg:. Just below it, insert this code:
cmpi.b #96,(v_hbla_line).w
bcc.s @update
bra.w @end
@update:
jsr SS_LoadWalls
Now let's go to VBla_16:, and, again, find @nochg:. Just below it, insert the same code block as above.
Now we need to go to GM_Special: and find SS_ClrNemRam:. You will see this chunk of code.
SS_ClrNemRam:
move.l d0,(a1)+
dbf d1,SS_ClrNemRam ; clear Nemesis buffer
clr.b (f_wtr_state).w
clr.w (f_restart).w
moveq #palid_Special,d0
bsr.w PalLoad1 ; load special stage palette
jsr SS_Load ; load SS layout data
move.l #0,(v_screenposx).w
move.l #0,(v_screenposy).w
move.b #9,(v_objspace).w ; load special stage Sonic object
bsr.w PalCycle_SS
clr.w (v_ssangle).w ; set stage angle to "upright"
Just below the line that loads Special Stage Sonic, add this line:
move.b #$FF,(v_ssangleprev).w ; fill previous angle with obviously false value to force an update
This v_ssangleprev is a new RAM variable, so we are going to need to add an equate for it. In Variables.asm, add it wherever you have a free byte of RAM.
Next we are going to SS_AniWallsRings: to change how the walls are animated. Remove the following chunk right at the start of the section:
lea ($FF400C).l,a1
moveq #0,d0
move.b (v_ssangle).w,d0
lsr.b #2,d0
andi.w #$F,d0
moveq #$23,d1
loc_1B2A4:
move.w d0,(a1)
addq.w #8,a1
dbf d1,loc_1B2A4
Next, go to the end of the section where you will find a data table that looks like this:
; ===========================================================================
SS_WaRiVramSet: dc.w $142, $6142, $142, $142, $142, $142, $142, $6142
dc.w $142, $6142, $142, $142, $142, $142, $142, $6142
...
Just above that, we are going to create a new subroutine to load the wall art dynamically. Insert this code above the table:
SS_LoadWalls:
moveq #0,d0
move.b (v_ssangle).w,d0 ; get the Special Stage angle
lsr.b #2,d0 ; modify so it can be used as a frame ID
andi.w #$F,d0
cmp.b (v_ssangleprev).w,d0 ; does the modified angle match the recorded value?
beq.s @return ; if so, branch
lea ($C00000).l,a6
lea (Nem_SSWalls).l,a1 ; load wall art
move.w d0,d1
lsl.w #8,d1
add.w d1,d1
add.w d1,a1
locVRAM $2840 ; VRAM address
move.w #$F,d1 ; number of 8x8 tiles
jsr LoadTiles
move.b d0,(v_ssangleprev).w ; record the modified angle for comparison
@return:
rts
SS_LoadWalls uses the new RAM variable to determine when new tiles are loaded into VRAM. Every time tiles are loaded, $10 tiles will be loaded to VRAM at address $2840. You can find this in a Regen VRAM dump at location 142, the same location that the tileset was previously loaded to. The big difference now, is that you have room for an additional 233 ($E9) tiles for use. You can use that for plenty of things, most notably a HUD! Now there is one final thing we need to do. We still have a Pattern Loading Cue for Special Stage walls to be loaded in this same spot... Having this means that all 256 tiles will be loaded to VRAM, meaning that not only will we not have the desired free VRAM, but even worse, the Bumper art will be overwritten! To fix this, simply go to inc\Pattern Load Cues.asm and at PLC_SpecialStage:, remove the following line:
plcm Nem_SSWalls, $2840 ; walls
That should be it! Enjoy the freed VRAM! Also note that another advantage to this method is that you can freely add more art to make more in-between frames to make the rotation even smoother!
(Additional fix by Cinossu)
Speaking of smoother rotation, you may also want to implement this change as well. Go to SS_ShowLayout: and take out this line:
andi.b #$FC,d0
|Dynamic Special Stage Walls system in Sonic 1]]