One of the key features of the up-coming homebrew game Alisha's Adventure is its animation engine. I would like to see other people using my animation engine, but it's so complicated that it makes it difficult for other people to understand. I've actually spent more time trying to make this routine easier for other people to understand, than it actually took me to get this routine working in the first place.
I'm going to break down my code in separate parts with a paragraph explaining, so you don't have to understand every single line of code to understand how it works.
First, I would like to point out that if my animation engine is used in another homebrew game, you would either have to use cartridge SRAM or tweak the code slightly to avoid any long addressing mode issues. Here is a diagram of the memory layout of Alisha's Adventure. $A0 is the default bank for the game.
This first section loops through every game object and determines if the animation frame can update or not, based on if the update flag is set, how many copies of the animation frame is currently onscreen and how much DMA time is left over. It uses a hash table to keep track of up to 1024 animation frames.
Some animated sprites use "indirect metasprite table" mode which means that each animation frame has a different sprite shape.
Please note that the vram_size variable is stored directly after attributes. This was done to copy and paste attributes and vram_size at the same time.
//metasprite format:
//$0000: unused in dynamic animation code
//$0002: bit 15 enables "sram" mode (see below) or indirect metasprite table mode
//$0004: unused in dynamic animation code
//$0006: ROM address
//$0008: ROM bank, bit 15 enables single 32x32 sprite, bit 14 enables single 16x16 sprite
//$000a: sprite ID number
//$000c: sprite attribute top byte
//$000d: total sprite pattern size in 16x16 units
//$000e: hhhvvvtt or indirect metasprite table
//h: horizontal run
//v: vertical run
//t: 00: exit
// 01: 16x16
// 10: 32x32
// 11: repeat last CHR
//$000f: x coordinate
//$0010: y coordinate
//$0011: same as $000e
//indirect metasprite format:
//$0000: same as $000d
define CHR_ROM_address({temp})
define CHR_ROM_bank({temp2})
define previous_sprite_index({temp3})
define metasprite_data_index({temp4})
define CHR_VRAM_address({temp5})
define vertical_sprite_run({temp6})
define horizontal_sprite_run({temp7})
define pixel_increment({temp8})
define temp_animation_index({temp9})
define attributes({temp10}) //IMPORTANT! Both attributes AND vram size NEED to be
define vram_size({temp11}) //adjacent to each other
animation:
stz {temp_x} //make sure top byte is always zero
stz {temp_y} //make sure top byte is always zero
stz {vertical_sprite_run} //make sure top byte is always zero
stz {horizontal_sprite_run} //make sure top byte is always zero
stz {pixel_increment} //make sure top byte is always zero
stz {attributes} //make sure bottom byte is always zero
stz {vram_size} //make sure top byte is always zero
stz {temp_animation_index} //make sure top byte is always zero
lda {first_object_to_dma}
bne +
lda {active_slot_pointer} //if "first object to dma" got destroyed, use first active object instead
+;
sta {last_object_to_dma}
stz {first_object_to_dma}
-;
tcd
lda {object_traits} //sprite is animated if either animation update flags are set
bit #$00c0 //bit 6 is for normal update, bit 7 is for updates with 180 degree flip
beq end_animation
ldy {metasprite_request}
bne + //if "metasprite request" is blank, don't draw sprite
jsr clear_vram_slot //clear whatever sprites were drawn last frame
stz {frame_id}
bra end_animation
+;
sep #$21
lda $000d,y
adc {total_dma_legnth} //check if there is enough DMA time for sprite
cmp #$20
bcc do_animation
rep #$20
lda {first_object_to_dma} //if DMA time is up, and "first object to dma" is empty
bne end_animation //make this the "first object to dma"
tdc
sta {first_object_to_dma}
end_animation:
lda {next_slot}
bne +
lda {active_slot_pointer} //if it is the last slot, loop to the beginning of object tables
+;
cmp {last_object_to_dma}
bne -
rts
This next section gets it ready for decoding the next frame that will be shown onscreen.
Please note that when it stores to the top byte of attributes, it ALSO stores the bottom byte of vram_size as well
/////////////////////////////////////////////////////////////////////////////////////
//Setup decoding of metasprite
/////////////////////////////////////////////////////////////////////////////////////
do_animation:
stz.b {180_degree_flip}+1 //do some flipping correction in case of rotating sprites
lda #$c0 //if bit 7 of object traits is set
bit {object_traits}
bpl +
sta.b {180_degree_flip}+1
+;
trb {object_traits} //clear animation update flags
rep #$20
jsr clear_vram_slot //clear previous animation frame
ldy {metasprite_request}
lda $000a,y
clc
adc {animation_index}
sta {frame_id} //find animation frame number
tax
lda {animation_copies},x //if animation frame is a duplicate
beq + //no extra work is needed
inc
sta {animation_copies},x
bra end_animation
+;
inc
sta {animation_copies},x
lda {metasprite_linked_list_pointer}
sta {animation_chr},x
tax
lda $000c,y //IMPORTANT! This stores top half of
sta.w {attributes}+1 //attributes AND lower half of vram size
lda $0008,y
sta {CHR_ROM_bank}
bpl +
jmp large_single_sprite //if upper byte of bank is $80, or $40, then it is a single sprite
+; //if $00, then a metasprite
cmp #$4000 //$80 and $40 are deliberately chosen to be the DMA length
bcc +
jmp single_small_sprite
+;
sep #$20
lda {animation_index}
sta {temp_animation_index}
lsr
sta $4202
lda {vram_size}
sta $4203 //multiply frame of the animation by ROM pattern size in units of 16x16s
rep #$20
phd
lda #$0000
tcd //move DP to $0000, for optimizations
lda $4216
xba
lsr
bcc +
adc #$7fff
+;
adc $0006,y
sta.b {CHR_ROM_address}
lda $0002,y
bpl +
tya //indirect metasprite table mode
clc
adc.b {temp_animation_index}
tay
lda $000e,y
sec
sbc #$000d
tay
+;
sep #$20
lda $000d,y
sta.b {vram_size}
clc
adc.b {total_dma_legnth}
sta.b {total_dma_legnth} //add to total dma usage
lda #$03
trb.b {vram_size} //clears lowest 2 bits from vram size, to round it down to the closest multiple of 4
Now here's the most important part. This next part copies the sprite data into a temporary buffer which will later be read by the OAM drawing code. These are stored in a linked list format to save memory.
Since I'm only using 16x16 and 32x32 sprites, odd rows and collumns of VRAM are never used, which allows me to use the extra bits to record the size bit, and a glitch bit.
hvppcccnnnnGnnnS
S: size bit G: glitch bit
/////////////////////////////////////////////////////////////////////////////
//This next part is where the metasprite decoding loop begins
/////////////////////////////////////////////////////////////////////////////
macro dma_large_slot() {
jsr find_large_slot
sta {CHR_VRAM_address}
bcs +
asl #4 //shifting clears carry
ora #$4000 //convert sprite name to VRAM address by multiplying by 16 and adding $4000
ldy.b {dma_updates}
sta {dma_destination},y
adc #$0100
sta {dma_destination_2},y
adc #$0100
sta {dma_destination}+2,y
adc #$0100
sta {dma_destination_2}+2,y
lda.b {CHR_ROM_address}
sta {dma_address},y
adc #$0100
sta {dma_address}+2,y
adc #$0100
sta.b {CHR_ROM_address}
lda.b {CHR_ROM_bank}
ora #$8000 //dma length is the high-byte of dma bank
sta {dma_bank},y
sta {dma_bank}+2,y
tya
clc
adc #$0004
sta.b {dma_updates}
+;
}
decode_metasprite_loop:
lda $000e,y
bne +
rep #$20
stx {metasprite_linked_list_pointer} //loop ends when $0010,y is 0
ldx.b {previous_sprite_index}
pld
lda #$ffff
sta.w {next_sprite},x //mark end of metasprite
jmp end_animation //this is where the game exits the routine
+;
sty.b {metasprite_data_index}
lda $000e,y
and #$e0
beq +
asl
rol #3
+;
sta.b {horizontal_sprite_run}
lda $000f,y
-;
sta.b {temp_x}
sta.w {sprite_x},x
lda $000e,y
lsr #2
and #$07
sta.b {vertical_sprite_run}
lda $0010,y
-;
sta.b {temp_y}
sta.w {sprite_y},x
lda $000e,y //0 is for no sprites (see above), 1 is for 16x16 sprite, 2 is for 32x32
and #$03 //3 is for repeat last CHR, whatever size it is
cmp #$03
beq find_vram_slot_done
dec
bne +
jmp small_slot //decrement value, so 0 is 16x16, 1 is for 32x32 and 2 is for repeat
+;
lda #$20
sta.b {pixel_increment} //set pixel increment to 32 pixels
lda.b {vram_size}
sec
sbc #$04
sta.b {vram_size}
rep #$20
dma_large_slot()
inc.b {CHR_VRAM_address}
find_vram_slot_done:
rep #$20
lda.b {CHR_VRAM_address}
ora.b {attributes}
sta.w {sprite_attributes},x
ldy.b {metasprite_data_index}
stx.b {previous_sprite_index}
lda.w {next_sprite},x
tax
sep #$20
dec.b {vertical_sprite_run} //decreases vertical_sprite_run until all sprites
bmi + //in the vertical run are put on linked list
lda.b {temp_x}
sta.w {sprite_x},x
lda.b {temp_y}
clc
adc.b {pixel_increment} //add 16 or 32 to y coordinates
jmp -
+;
dec.b {horizontal_sprite_run} //decreases horizontal_sprite_run until all sprites
bmi + //in the horizontal run are put on linked list
lda.b {temp_x}
clc
adc.b {pixel_increment} //add 16 or 32 to x coordinates
jmp --
+;
iny #3
jmp decode_metasprite_loop //add 6 to y index to fetch next sprite(s) in metasprite
Now here's the section for small sprites. If there 4 or more sprites left, it looks for a 32x32 slot and splits it into 4 16x16 slots. If there are 3 or less sprites left, it looks for individual 16x16 slots.
small_slot: //this finds 16x16 VRAM slot
lda #$10
sta.b {pixel_increment}
lda.b {vram_size}
bne share_large_slot
dma_small_slot:
rep #$20
jsr find_small_slot
sta {CHR_VRAM_address} //store sprite name in temp6
bcs +
asl #4 //shifting clears carry
ora #$4000 //convert sprite name to VRAM address by multiplying by 16 and adding $4000
ldy.b {dma_updates}
sta {dma_destination},y
adc #$0100
sta {dma_destination_2},y
lda.b {CHR_ROM_address}
sta {dma_address},y
adc #$0080
sta.b {CHR_ROM_address} //each consecutive sprite pattern is place immediately after previous in ROM
lda.b {CHR_ROM_bank}
ora #$4000
sta {dma_bank},y //dma length is the high-byte of dma bank
iny #2
sty.b {dma_updates}
+;
jmp find_vram_slot_done
share_large_slot:
dec.b {vram_size}
bit #$03
bne ++
rep #$20
dma_large_slot()
jmp find_vram_slot_done //macro uses an extra + label
+;
lda.b {CHR_VRAM_address} //the code chooses one of the 4 subslots inside a 32x32 slot
eor #$02
bit #$02
bne +
eor #$20
+;
sta.b {CHR_VRAM_address}
jmp find_vram_slot_done
This next section is for single-sprite animation frames. These have separate routines, as a way to keep CPU cycles down when dealing with single-sprite animation frames.
sram_bank_crossing:
lda {animation_index} //this is for software rotating sprites that need sram bank jumping
xba //only used for 32x32 non-metasprites
and #$ff00
-;
cmp #$2000
bcc +
sbc #$2000
inc {CHR_ROM_bank}
bra -
large_single_sprite: //this is for non-metasprites
lda #$0004
clc
adc {total_dma_legnth}
sta {total_dma_legnth}
lda $0002,y
bmi sram_bank_crossing
lda {animation_index}
xba
and #$ff00
clc
+;
adc $0006,y
sta {CHR_ROM_address}
jsr find_large_slot
ora {attributes}
inc
sta.w {sprite_attributes},x //this code is almost identical to the dma large slot routine
bcs + //but with speed optimizations
and #$01cc
asl #4 //shifting clears carry
ora #$4000 //convert sprite name to VRAM address by multiplying by 16 and adding $4000
ldy {dma_updates}
sta {dma_destination},y
adc #$0100
sta {dma_destination_2},y
adc #$0100
sta {dma_destination}+2,y
adc #$0100
sta {dma_destination_2}+2,y
lda {CHR_ROM_address}
sta {dma_address},y
adc #$0100
sta {dma_address}+2,y
lda {CHR_ROM_bank}
sta {dma_bank},y
sta {dma_bank}+2,y
tya
clc
adc #$0004
sta {dma_updates}
+;
lda #$0070
sta.w {sprite_y},x
sta.w {sprite_x},x
lda.w {next_sprite},x
sta {metasprite_linked_list_pointer}
lda #$ffff
sta.w {next_sprite},x
jmp end_animation //this is where the game exits the routine
single_small_sprite:
inc {total_dma_legnth}
lda {animation_index}
xba
and #$ff00
lsr #2
adc $0006,y
sta {CHR_ROM_address}
jsr find_small_slot //finds open 16x16 VRAM slot
ora {attributes}
sta.w {sprite_attributes},x //this code is almost identical to the dma small slot routine
bcs + //but with speed optimizations
and #$01ee
asl #4 //shifting clears carry
ora #$4000 //convert sprite name to VRAM address by multiplying by 16 and adding $4000
ldy {dma_updates}
sta {dma_destination},y
adc #$0100
sta {dma_destination_2},y
lda {CHR_ROM_address}
sta {dma_address},y
lda {CHR_ROM_bank}
sta {dma_bank},y //dma length is the high-byte of dma bank
iny #2
sty {dma_updates}
+;
lda #$0078
sta.w {sprite_y},x
sta.w {sprite_x},x
lda.w {next_sprite},x
sta {metasprite_linked_list_pointer}
lda #$ffff
sta.w {next_sprite},x
jmp end_animation //this is where the game exits the routine
...and last but not least, the routines for finding and clearing VRAM slots. Here are the slot finding routines.
macro check_large_slot_flag(n,label) {
ldx.w {vram_slot_flags}+{n}
lda vram_slot_lut_c,x
bne {label}
}
macro found_large_slot_flag(n) {
ldy.b #{n}
bra found_large_slot
}
find_large_slot:
phx
sep #$30
check_large_slot_flag(15,found_large_slot_0)
check_large_slot_flag(14,found_large_slot_1)
check_large_slot_flag(13,found_large_slot_2)
check_large_slot_flag(12,found_large_slot_3)
check_large_slot_flag(11,found_large_slot_4)
check_large_slot_flag(10,found_large_slot_5)
check_large_slot_flag(9,found_large_slot_6)
check_large_slot_flag(8,found_large_slot_7)
check_large_slot_flag(7,found_large_slot_8)
check_large_slot_flag(6,found_large_slot_9)
check_large_slot_flag(5,found_large_slot_10)
check_large_slot_flag(4,found_large_slot_11)
check_large_slot_flag(3,found_large_slot_12)
ldx.w {vram_slot_flags}+2 //this weird gap is because of
lda vram_slot_lut_c,x //branching limits
beq +
bra found_large_slot_13
found_large_slot_0:
found_large_slot_flag(15)
found_large_slot_1:
found_large_slot_flag(14)
found_large_slot_2:
found_large_slot_flag(13)
+;
check_large_slot_flag(1,found_large_slot_14)
check_large_slot_flag(0,found_large_slot_15)
jmp invalid_large_slot
found_large_slot_3:
found_large_slot_flag(12)
found_large_slot_4:
found_large_slot_flag(11)
found_large_slot_5:
found_large_slot_flag(10)
found_large_slot_6:
found_large_slot_flag(9)
found_large_slot_7:
found_large_slot_flag(8)
found_large_slot_8:
found_large_slot_flag(7)
found_large_slot_9:
found_large_slot_flag(6)
found_large_slot_10:
found_large_slot_flag(5)
found_large_slot_11:
found_large_slot_flag(4)
found_large_slot_12:
found_large_slot_flag(3)
found_large_slot_13:
found_large_slot_flag(2)
found_large_slot_14:
found_large_slot_flag(1)
found_large_slot_15:
ldy #$00
found_large_slot:
cmp #$0f
beq +
ora {vram_slot_flags}-$a00000,y
sta {vram_slot_flags}-$a00000,y
lda vram_slot_lut_a,y
ora #$02
rep #$30
and #$00ff
asl //shifting left clears carry to signal a valid VRAM location
plx
rts
+;
ora {vram_slot_flags}-$a00000,y
sta {vram_slot_flags}-$a00000,y
lda vram_slot_lut_a,y
rep #$30
and #$00ff
asl //shifting left clears carry to signal a valid VRAM location
plx
rts
invalid_large_slot:
rep #$31
lda {CHR_ROM_address}
adc #$0200
sta {CHR_ROM_address} //this fixes a glitch
lda #$0010
sec //set carry to signal an invalid VRAM location
plx
rts
macro check_small_slot_flag(n,label) {
cmp.w {vram_slot_flags}+{n}
bne {label}
}
macro found_small_slot_flag(n) {
ldy.w #{n}
bra +
}
find_small_slot:
lda #$ffff
check_small_slot_flag(0,found_small_slot_0)
check_small_slot_flag(2,found_small_slot_1)
check_small_slot_flag(4,found_small_slot_2)
check_small_slot_flag(6,found_small_slot_3)
check_small_slot_flag(8,found_small_slot_4)
check_small_slot_flag(10,found_small_slot_5)
check_small_slot_flag(12,found_small_slot_6)
check_small_slot_flag(14,found_small_slot_7)
lda #$0010
rts //comparing sets carry to signal an invalid VRAM location
found_small_slot_0:
found_small_slot_flag(0)
found_small_slot_1:
found_small_slot_flag(2)
found_small_slot_2:
found_small_slot_flag(4)
found_small_slot_3:
found_small_slot_flag(6)
found_small_slot_4:
found_small_slot_flag(8)
found_small_slot_5:
found_small_slot_flag(10)
found_small_slot_6:
found_small_slot_flag(12)
found_small_slot_7:
ldy #$000e
+;
phx
sep #$30
cmp {vram_slot_flags}-$a00000,y
bne +
iny
+;
lda {vram_slot_flags}-$a00000,y
tax
inc
ora {vram_slot_flags}-$a00000,y
sta {vram_slot_flags}-$a00000,y
lda vram_slot_lut_a,y
ora vram_slot_lut_b,x
rep #$30
and #$00ff
asl //shifting left clears carry to signal a valid VRAM location
plx
rts
vram_slot_lut_a:
db $00,$04,$20,$24,$40,$44,$60,$64
db $80,$84,$a0,$a4,$c0,$c4,$e0,$e4
vram_slot_lut_c:
db $f0,$f0,$f0,$f0,$f0,$f0,$f0,$f0,$f0,$f0,$f0,$f0,$f0,$f0,$f0,$f0 //lut b is in bank $cc
db $0f,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00
db $0f,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00
db $0f,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00
db $0f,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00
db $0f,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00
db $0f,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00
db $0f,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00
db $0f,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00
db $0f,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00
db $0f,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00
db $0f,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00
db $0f,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00
db $0f,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00
db $0f,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00
db $0f,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00
Here is the VRAM slot clearing routine.
////////////////////////////////////////////////////////////////////////////////////////////////////
//clear metasprite from metasprite cache
////////////////////////////////////////////////////////////////////////////////////////////////////
clear_vram_slot: //this routine clears the VRAM slots of the previous animation frame
ldx {frame_id}
beq clear_vram_slot_done //if previous frame ID was 0, there was no previous frame, and there for no extra work
dec.w {animation_copies},x //decrement number of copies
bne clear_vram_slot_done //only clear metasprite if no duplicates exist
lda {animation_chr},x //saves beginning of metasprite linked list table
pha
-;
tay
lda {sprite_attributes}-$a00000,y
bit #$0010 //check for glitched sprite
bne ++
bit #$0001
beq +
jsr clear_large_slot
bra ++
+;
jsr clear_small_slot
+;
lda {next_sprite}-$a00000,y
cmp #$ffff
bne - //exit when it comes across a $ffff marker
lda {metasprite_linked_list_pointer}
sta {next_sprite}-$a00000,y //link beginning of available linked list to end of the linked list that's been cleared
pla
sta {metasprite_linked_list_pointer} //cleared linked list becomes the start of available linked list
clear_vram_slot_done:
rts
clear_large_slot:
and #$01cc
lsr
tax
sep #$20
bit #$02
bne +
lda vram_slot_lut,x
tax
lda {vram_slot_flags},x
and #$f0
sta {vram_slot_flags},x
rep #$20
rts
+;
lda vram_slot_lut,x
tax
lda {vram_slot_flags},x
and #$0f
sta {vram_slot_flags},x
rep #$20
rts
clear_small_slot:
phy
and #$01ee
lsr
sep #$30
tax
lda vram_slot_lut,x
tay
lda {vram_slot_flags}-$a00000,y
and vram_slot_table_bit_reset,x
sta {vram_slot_flags}-$a00000,y
rep #$30
ply
rts
Some of the lookup tables needed were moved to bank $cc in order to save space in bank $a0.
vram_slot_lut_b:
db $00,$01,$00,$10,$00,$01,$00,$11,$00,$01,$00,$10,$00,$01,$00,$02
db $00,$01,$00,$10,$00,$01,$00,$11,$00,$01,$00,$10,$00,$01,$00,$03
db $00,$01,$00,$10,$00,$01,$00,$11,$00,$01,$00,$10,$00,$01,$00,$02
db $00,$01,$00,$10,$00,$01,$00,$11,$00,$01,$00,$10,$00,$01,$00,$12
db $00,$01,$00,$10,$00,$01,$00,$11,$00,$01,$00,$10,$00,$01,$00,$02
db $00,$01,$00,$10,$00,$01,$00,$11,$00,$01,$00,$10,$00,$01,$00,$03
db $00,$01,$00,$10,$00,$01,$00,$11,$00,$01,$00,$10,$00,$01,$00,$02
db $00,$01,$00,$10,$00,$01,$00,$11,$00,$01,$00,$10,$00,$01,$00,$13
db $00,$01,$00,$10,$00,$01,$00,$11,$00,$01,$00,$10,$00,$01,$00,$02
db $00,$01,$00,$10,$00,$01,$00,$11,$00,$01,$00,$10,$00,$01,$00,$03
db $00,$01,$00,$10,$00,$01,$00,$11,$00,$01,$00,$10,$00,$01,$00,$02
db $00,$01,$00,$10,$00,$01,$00,$11,$00,$01,$00,$10,$00,$01,$00,$12
db $00,$01,$00,$10,$00,$01,$00,$11,$00,$01,$00,$10,$00,$01,$00,$02
db $00,$01,$00,$10,$00,$01,$00,$11,$00,$01,$00,$10,$00,$01,$00,$03
db $00,$01,$00,$10,$00,$01,$00,$11,$00,$01,$00,$10,$00,$01,$00,$02
db $00,$01,$00,$10,$00,$01,$00,$11,$00,$01,$00,$10,$00,$01,$00,$13
vram_slot_lut:
db $00,$00,$00,$00,$01,$01,$01,$01,$00,$00,$00,$00,$01,$01,$01,$01
db $00,$00,$00,$00,$01,$01,$01,$01,$00,$00,$00,$00,$01,$01,$01,$01
db $02,$02,$02,$02,$03,$03,$03,$03,$02,$02,$02,$02,$03,$03,$03,$03
db $02,$02,$02,$02,$03,$03,$03,$03,$02,$02,$02,$02,$03,$03,$03,$03
db $04,$04,$04,$04,$05,$05,$05,$05,$04,$04,$04,$04,$05,$05,$05,$05
db $04,$04,$04,$04,$05,$05,$05,$05,$04,$04,$04,$04,$05,$05,$05,$05
db $06,$06,$06,$06,$07,$07,$07,$07,$06,$06,$06,$06,$07,$07,$07,$07
db $06,$06,$06,$06,$07,$07,$07,$07,$06,$06,$06,$06,$07,$07,$07,$07
db $08,$08,$08,$08,$09,$09,$09,$09,$08,$08,$08,$08,$09,$09,$09,$09
db $08,$08,$08,$08,$09,$09,$09,$09,$08,$08,$08,$08,$09,$09,$09,$09
db $0a,$0a,$0a,$0a,$0b,$0b,$0b,$0b,$0a,$0a,$0a,$0a,$0b,$0b,$0b,$0b
db $0a,$0a,$0a,$0a,$0b,$0b,$0b,$0b,$0a,$0a,$0a,$0a,$0b,$0b,$0b,$0b
db $0c,$0c,$0c,$0c,$0d,$0d,$0d,$0d,$0c,$0c,$0c,$0c,$0d,$0d,$0d,$0d
db $0c,$0c,$0c,$0c,$0d,$0d,$0d,$0d,$0c,$0c,$0c,$0c,$0d,$0d,$0d,$0d
db $0e,$0e,$0e,$0e,$0f,$0f,$0f,$0f,$0e,$0e,$0e,$0e,$0f,$0f,$0f,$0f
db $0e,$0e,$0e,$0e,$0f,$0f,$0f,$0f,$0e,$0e,$0e,$0e,$0f,$0f,$0f,$0f
vram_slot_table_bit_reset:
db $fe,$fd,$ef,$df,$fe,$fd,$ef,$df,$fe,$fd,$ef,$df,$fe,$fd,$ef,$df
db $fb,$f7,$bf,$7f,$fb,$f7,$bf,$7f,$fb,$f7,$bf,$7f,$fb,$f7,$bf,$7f
db $fe,$fd,$ef,$df,$fe,$fd,$ef,$df,$fe,$fd,$ef,$df,$fe,$fd,$ef,$df
db $fb,$f7,$bf,$7f,$fb,$f7,$bf,$7f,$fb,$f7,$bf,$7f,$fb,$f7,$bf,$7f
db $fe,$fd,$ef,$df,$fe,$fd,$ef,$df,$fe,$fd,$ef,$df,$fe,$fd,$ef,$df
db $fb,$f7,$bf,$7f,$fb,$f7,$bf,$7f,$fb,$f7,$bf,$7f,$fb,$f7,$bf,$7f
db $fe,$fd,$ef,$df,$fe,$fd,$ef,$df,$fe,$fd,$ef,$df,$fe,$fd,$ef,$df
db $fb,$f7,$bf,$7f,$fb,$f7,$bf,$7f,$fb,$f7,$bf,$7f,$fb,$f7,$bf,$7f
db $fe,$fd,$ef,$df,$fe,$fd,$ef,$df,$fe,$fd,$ef,$df,$fe,$fd,$ef,$df
db $fb,$f7,$bf,$7f,$fb,$f7,$bf,$7f,$fb,$f7,$bf,$7f,$fb,$f7,$bf,$7f
db $fe,$fd,$ef,$df,$fe,$fd,$ef,$df,$fe,$fd,$ef,$df,$fe,$fd,$ef,$df
db $fb,$f7,$bf,$7f,$fb,$f7,$bf,$7f,$fb,$f7,$bf,$7f,$fb,$f7,$bf,$7f
db $fe,$fd,$ef,$df,$fe,$fd,$ef,$df,$fe,$fd,$ef,$df,$fe,$fd,$ef,$df
db $fb,$f7,$bf,$7f,$fb,$f7,$bf,$7f,$fb,$f7,$bf,$7f,$fb,$f7,$bf,$7f
db $fe,$fd,$ef,$df,$fe,$fd,$ef,$df,$fe,$fd,$ef,$df,$fe,$fd,$ef,$df
db $fb,$f7,$bf,$7f,$fb,$f7,$bf,$7f,$fb,$f7,$bf,$7f,$fb,$f7,$bf,$7f