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.

/uploads/memory_layout-1721407224007.png

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