dynamic animation

This is an archive of a topic from NESdev BBS, taken in mid-October 2019 before a server upgrade.
View original topic
dynamic animation
by on (#62952)
This morning I had the idea of programming my own little dynamic animation engine that allows up to 48 16x16 sprites to be animated at 60 fps directly from the cart without the v-ram limitations.

Sure 48 sprites isn't the max amount of sprites the Snes can display, but I'm limiting the amount to 48 sprites due to the 6460 byte dma limit.

48*16*16*4/8=6144
170*38=6460

To eliminate overhead during v-blank, the sprites patterns will be copied onto a pattern table buffer, and the 6k pattern table will be dma to v-ram during v-blank at once.

by on (#62953)
Do whatever is best for your particular game design. I would be careful about trying to use every last potential piece of VBLANK time. You may want a slight margin for error. I'm guessing you've considered what other things need VBLANK time besides the actual transfer time for DMA?

by on (#62955)
MottZilla wrote:
I would be careful about trying to use every last potential piece of VBLANK time.


I'd say not trying to use all the VBLANK time you got would be a waste.
What I usually do is to set up a dma queue that tries to upload as much as possible during blanking, but check how much time is left after each transfer, and postpone the remaining queued transfers if a certain threshold is exceeded.

Also, it's quite common to enter blanking early or exit blanking late, but not everybody is fond of that.
The trade-off here being screen size vs. amount of vram writes per frame, of course.

by on (#62956)
Unfortunately my game has more than 48 sprites onscreen already.

by on (#62957)
Keep commonly used sprite cels (such as projectiles and identical-looking enemies) in VRAM for a longer period of time, and devote only a portion of VRAM to demand-loaded sprite cels.

by on (#62958)
Which is better?

first 6k dynamic
last 10k standard

or

first 4k dynamic
last 12k standard

by on (#62961)
d4s wrote:
MottZilla wrote:
I would be careful about trying to use every last potential piece of VBLANK time.


I'd say not trying to use all the VBLANK time you got would be a waste.
What I usually do is to set up a dma queue that tries to upload as much as possible during blanking, but check how much time is left after each transfer, and postpone the remaining queued transfers if a certain threshold is exceeded.

Also, it's quite common to enter blanking early or exit blanking late, but not everybody is fond of that.
The trade-off here being screen size vs. amount of vram writes per frame, of course.


While I agree you don't want to waste it, but you certainly don't want to push too far. I'm not saying let some huge amount of time go, but I'm saying you should give yourself a small bit of breathing room. For example rather than trying to use 100% of your VBLANK time doing whatever, save a tiny bit, maybe 1% or 0.5% of time as just a small buffer.

But it all boils down to what you need in your particular game. Some games are very simple and wouldn't need many of the features of the SNES at all. Others push the SNES much harder.

I think this topic in general is about what I recall being a relatively low amount of memory available for sprites and how to get the most out of it. For player characters like Player 1&2, which in 1v1 fighting games works as well, it's easy to DMA required tiles for each frame as needed. But it gets harder for the duplicate enemys and other objects. For example Metal Slug has the green soldiers with tons of animation frames. The SNES could easily handle 1 of those soldiers by DMAing the needed frame in. And I guess what you are thinking is doing the same thing but for a much larger size of characters. If you get it to work that's great for ultra smooth animation that is possible.

It'll be interesting to see what results you come up with. Particularly if you do try to push it to the limit, seeing just how many fully animated objects you can have moving around.

by on (#62962)
Without seeing a complete mock-up of your game in motion, it's hard to say in advance how to split it up. You could allocate space for a bunch of different tiers of sprite cels:
  • Things that get reloaded every frame or every two frames (player character sprite)
  • Things that get reloaded on player's demand (weapon projectiles; pause menu components; HUD indicators)
  • Things that get reloaded when a particular kind of sprite enters the camera (a squad of identical mooks)
  • Things that get reloaded per level (common projectiles; level-specific particles; more static HUD items such as score markers)

by on (#62963)
I just noticed something I overlooked. 6k dynamic sprites wouldn't work with 128 oam sprite entries. It would work with 48 oam sprite entries, but not 128. I guess I'm going with 5k so I can dma the entire oam and still have time for scrolling and other stuff like that.

At first using 30 fps animation instead of 60 fps seems like it would be a good solution since 30 fps looks almost as good as 60 fps and takes half the bandwidth, but 30 fps causes implementation problems. Let's say one enemy takes up sprites 12-15 and the next enemy takes up sprites 16-19. On the second frame of the frame pair, the first enemy dies, and the second enemy gets bumped down to sprites 12-15. For 1/60 second, enemy #2 has the wrong frame.

by on (#62965)
psycopathicteen wrote:
At first using 30 fps animation instead of 60 fps seems like it would be a good solution since 30 fps looks almost as good as 60 fps and takes half the bandwidth, but 30 fps causes implementation problems. Let's say one enemy takes up sprites 12-15 and the next enemy takes up sprites 16-19. On the second frame of the frame pair, the first enemy dies, and the second enemy gets bumped down to sprites 12-15. For 1/60 second, enemy #2 has the wrong frame.

Don't bump them down just because a cel slot has opened. Instead, mark the slot as unused and ready to allocate to another actor.

by on (#62966)
tepples wrote:
Don't bump them down just because a cel slot has opened. Instead, mark the slot as unused and ready to allocate to another actor.

You can use linked lists to keep track of used and unused slots.

by on (#62979)
I have a better idea. Only spawn and kill enemies on even frames. On even frames, upload first 5kB of patterns to backbuffer. On odd frames, upload second 5kB of patterns to backbuffer and switch buffers. Since this method causes the pattern table to lag 1 frame behind the oam, I will program a 1 frame delay for the oam to match the pattern table.

by on (#63040)
Just so you know, I'm not just making this dynamic animation engine for my own game, I encourage other people to play around with it, and see what other people could do with it. I'll post the source code when I'm ready, and I expect others to implement it in their own game engine. It will make me happy to see newcombers at homebrewing, being capable of producing animation better than released titles, right from the start.

Unlike other tricks like Mode-7 and transperancy that are impressive once or twice but lose their novelty, animation will always be impressive because it's an art form. When you see, for example, a transparent cloud doing an waving effects, it's just the waving effect that is impressive, not the game as a whole. When a game has consistantly high animation, it's not impressive because one specific thing is, the game is impressive overall.

That's just my theory of why I beleive animation is important. It's also why I admire Joakim Sandberg's indie games. Everytime I play one of his indie games I can't resist thinking to myself, "wouldn't it be nice if Super Nintendo games really looked like that!" It would also be cool if we can get Joakim to join the Snes homebrew scene.

by on (#63048)
psycopathicteen wrote:
Unlike other tricks like Mode-7 and transperancy that are impressive once or twice but lose their novelty, animation will always be impressive because it's an art form. When you see, for example, a transparent cloud doing an waving effects, it's just the waving effect that is impressive, not the game as a whole. When a game has consistantly high animation, it's not impressive because one specific thing is, the game is impressive overall.


I'd say it's no different, actually. Animation is just a piece of the game, and impressive animation doesn't make a game impressive overall, just as a fancy effect doesn't. "It's just the animation that's impressive, not the game as a whole," is as valid a statement as, "It's just the waving effect that is impressive, not the game as a whole".

Don't get me wrong, I love 2d animation and if your animation engine allows people to make their games better that's awesome. I'm just saying there are beautiful games out there that don't play very well are aren't super engaging.

by on (#63051)
I honestly find the vast majority of Snes games to be neither impressive nor fun. The old "it doesn't matter what it looks like, just as long as it plays good" statement doesn't help. It could've been a lot better than it was, and this is why I got into homebrewing.

by on (#63080)
I think we've been over some of the limitations they faced back in those days though. Remember many SNES games were limited to 4 megabits or 8 megabits of ROM. They didn't have 32 megabits available for them. They didn't have infinite time either.

It'll be great to see what you and others can do on the system now, but you do have to be fair to the developers of the day who created these games under restrictions of time, money, and experience. Really just limited resources in general.

by on (#63081)
We have restrictions of time too: school or a day job. We also have restrictions of memory: big EPROMs still cost money.

by on (#63082)
I have the time restriction of how long can I program before I'm all bummed out, and you can probably tell from my most recent posts, I have reached that limit.

Here's an early version of the dynamic animation source code. At address $7f8000 there is a sprite table with 40 16x16 sprites organized like this:

bytes 0-1
x coordinate

bytes 2-3
y coordinate

bytes 4-6
rom address of 16x16 sprite

byte 7
attributes

Load $7f8200 into oam and $7f0000 (5k bytes) into v-ram where the sprite pattern table starts.

Code:
macro move_row_sprite_patterns(a,b,c)
%move_16x16_sprite_pattern(0+<a>,0+<b>,0+<c>,0+<c>)
%move_16x16_sprite_pattern(8+<a>,64+<b>,4+<c>,2+<c>)
%move_16x16_sprite_pattern(16+<a>,128+<b>,8+<c>,4+<c>)
%move_16x16_sprite_pattern(24+<a>,192+<b>,12+<c>,6+<c>)
%move_16x16_sprite_pattern(32+<a>,256+<b>,16+<c>,8+<c>)
%move_16x16_sprite_pattern(40+<a>,320+<b>,20+<c>,10+<c>)
%move_16x16_sprite_pattern(48+<a>,384+<b>,24+<c>,12+<c>)
%move_16x16_sprite_pattern(56+<a>,448+<b>,28+<c>,14+<c>)
endmacro

macro move_16x16_sprite_pattern(a,b,c,d)
lda $7f8000+<a>
sta $7f8200+<c>
lda $7f8002+<a>
sta $7f8201+<c>
lda $7f8006+<a>
and #$fe00
ora #$0000+<d>
sta $7f8202+<c>
lda $7f8005+<a>
pha
plb
plb
lda $7f8004+<a>
tay
ldx #$0000+<b>
jsr move_sprite_pattern
endmacro

macro move_sprite_pattern(a)
lda $0000+<a>,y
sta $7f0000+<a>,x
lda $0002+<a>,y
sta $7f0002+<a>,x
lda $0004+<a>,y
sta $7f0004+<a>,x
lda $0006+<a>,y
sta $7f0006+<a>,x
lda $0008+<a>,y
sta $7f0008+<a>,x
lda $000a+<a>,y
sta $7f000a+<a>,x
lda $000c+<a>,y
sta $7f000c+<a>,x
lda $000e+<a>,y
sta $7f000e+<a>,x
lda $0010+<a>,y
sta $7f0010+<a>,x
lda $0012+<a>,y
sta $7f0012+<a>,x
lda $0014+<a>,y
sta $7f0014+<a>,x
lda $0016+<a>,y
sta $7f0016+<a>,x
lda $0018+<a>,y
sta $7f0018+<a>,x
lda $001a+<a>,y
sta $7f001a+<a>,x
lda $001c+<a>,y
sta $7f001c+<a>,x
lda $001e+<a>,y
sta $7f001e+<a>,x
endmacro









code:


%move_row_sprite_patterns(0,0,0)
%move_row_sprite_patterns(64,1024,32)
%move_row_sprite_patterns(128,2048,64)
%move_row_sprite_patterns(192,3072,96)
%move_row_sprite_patterns(256,4096,128)




lda #$8000
pha
plb
plb



sep #$30

lda #$f0
sta $7f82a1
sta $7f82a5
sta $7f82a9
sta $7f82ad
sta $7f82b1
sta $7f82b5
sta $7f82b9
sta $7f82bd
sta $7f82c1
sta $7f82c5
sta $7f82c9
sta $7f82cd
sta $7f82d1
sta $7f82d5
sta $7f82d9
sta $7f82dd
sta $7f82e1
sta $7f82e5
sta $7f82e9
sta $7f82ed
sta $7f82f1
sta $7f82f5
sta $7f82f9
sta $7f82fd
sta $7f8301
sta $7f8305
sta $7f8309
sta $7f830d
sta $7f8311
sta $7f8315
sta $7f8319
sta $7f831d
sta $7f8321
sta $7f8325
sta $7f8329
sta $7f832d
sta $7f8331
sta $7f8335
sta $7f8339
sta $7f833d
sta $7f8341
sta $7f8345
sta $7f8349
sta $7f834d
sta $7f8351
sta $7f8355
sta $7f8359
sta $7f835d
sta $7f8361
sta $7f8365
sta $7f8369
sta $7f836d
sta $7f8371
sta $7f8375
sta $7f8379
sta $7f837d
sta $7f8381
sta $7f8385
sta $7f8389
sta $7f838d
sta $7f8391
sta $7f8395
sta $7f8399
sta $7f839d
sta $7f83a1
sta $7f83a5
sta $7f83a9
sta $7f83ad
sta $7f83b1
sta $7f83b5
sta $7f83b9
sta $7f83bd
sta $7f83c1
sta $7f83c5
sta $7f83c9
sta $7f83cd
sta $7f83d1
sta $7f83d5
sta $7f83d9
sta $7f83dd
sta $7f83e1
sta $7f83e5
sta $7f83e9
sta $7f83ed
sta $7f83f1
sta $7f83f5
sta $7f83f9
sta $7f83fd





lda $7f8019
asl
asl
ora $7f8011
asl
asl
ora $7f8009
asl
asl
ora $7f8001
sta $7f8400

lda $7f8039
asl
asl
ora $7f8031
asl
asl
ora $7f8029
asl
asl
ora $7f8021
sta $7f8401

lda $7f8059
asl
asl
ora $7f8051
asl
asl
ora $7f8049
asl
asl
ora $7f8041
sta $7f8402

lda $7f8079
asl
asl
ora $7f8071
asl
asl
ora $7f8069
asl
asl
ora $7f8061
sta $7f8403

lda $7f8099
asl
asl
ora $7f8091
asl
asl
ora $7f8089
asl
asl
ora $7f8081
sta $7f8404

lda $7f80b9
asl
asl
ora $7f80b1
asl
asl
ora $7f80a9
asl
asl
ora $7f80a1
sta $7f8405

lda $7f80d9
asl
asl
ora $7f80d1
asl
asl
ora $7f80c9
asl
asl
ora $7f80c1
sta $7f8406

lda $7f80f9
asl
asl
ora $7f80f1
asl
asl
ora $7f80e9
asl
asl
ora $7f80e1
sta $7f8407

lda $7f8119
asl
asl
ora $7f8111
asl
asl
ora $7f8109
asl
asl
ora $7f8101
sta $7f8408

lda $7f8139
asl
asl
ora $7f8131
asl
asl
ora $7f8129
asl
asl
ora $7f8121
sta $7f8409
jmp over

move_sprite_pattern:
%move_sprite_pattern(0)
%move_sprite_pattern(32)
%move_sprite_pattern(512)
%move_sprite_pattern(544)
rts

over:

by on (#63093)
Does your assembler not support named constants and labels for variables? That would reduce mental load quite a bit :)

by on (#63098)
You could also get rid of all those STAs and used index registers for a loop that would look a whole lot nicer and use less ROM space.

True you have some sort of time restriction but it's hard to compare yours and developers at the time. Totally different groups you can't really compare fairly anyway. I hope you don't give up though. Take a break from it if you must, but hopefully you'll get back into it.

by on (#63187)
I would like to take a break, but there's just not enough going on in the Snes homebrew scene to entertain me, while I'm taking the break.

by on (#63607)
I'm finally finished with my dynamic animation engine. Now that these graphical limitations are out of the way, I can focus on gameplay without running into any technical issues again.

by on (#65339)
I know my code is still unreadeable. I've tried to clean it up for posting and this is the best I can do.






during v-blank

Code:
rep #$10
sep #$20

lda $0700
bne frame_0

lda #$80
sta $2115
ldx #$3000
stx $2116
ldx #$1801
stx $4300
ldx #$0000
stx $4302
lda #$7f
sta $4304
ldx #$1000
stx $4305
lda #$01
sta $420b
lda #$60
sta $2101





frame_0:
lda $0700
cmp #$01
bne frame_1

lda #$80
sta $2115
ldx #$3800
stx $2116
ldx #$1801
stx $4300
ldx #$0000
stx $4302
lda #$7f
sta $4304
ldx #$1000
stx $4305

lda #$01
sta $420b
lda #$70
sta $2101




frame_1:
lda $0700
cmp #$02
bne frame_2

lda #$80
sta $2115
ldx #$1000
stx $2116
ldx #$1801
stx $4300
ldx #$0000
stx $4302
lda #$7f
sta $4304
ldx #$1000
stx $4305
lda #$01
sta $420b
lda #$70
sta $2101



frame_2:
lda $0700
cmp #$03
bne frame_3

lda #$80
sta $2115
ldx #$1800
stx $2116
ldx #$1801
stx $4300
ldx #$0000
stx $4302
lda #$7f
sta $4304
ldx #$1000
stx $4305
lda #$01
sta $420b
lda #$60
sta $2101



frame_3:



during active display

Code:
macro process_row_sprites(a,b,c)
%process_sprite(0+<a>,0+<b>,0+<c>)
%process_sprite(8+<a>,4+<b>,2+<c>)
%process_sprite(16+<a>,8+<b>,4+<c>)
%process_sprite(24+<a>,12+<b>,6+<c>)
%process_sprite(32+<a>,16+<b>,8+<c>)
%process_sprite(40+<a>,20+<b>,10+<c>)
%process_sprite(48+<a>,24+<b>,12+<c>)
%process_sprite(56+<a>,28+<b>,14+<c>)
endmacro

macro process_row_sprites2(a,b)
%process_sprite2(0+<a>,0+<b>)
%process_sprite2(8+<a>,4+<b>)
%process_sprite2(16+<a>,8+<b>)
%process_sprite2(24+<a>,12+<b>)
%process_sprite2(32+<a>,16+<b>)
%process_sprite2(40+<a>,20+<b>)
%process_sprite2(48+<a>,24+<b>)
%process_sprite2(56+<a>,28+<b>)
endmacro

macro move_row_sprite_patterns(a,b)
%move_16x16_sprite_pattern(0+<a>,0+<b>)
%move_16x16_sprite_pattern(8+<a>,64+<b>)
%move_16x16_sprite_pattern(16+<a>,128+<b>)
%move_16x16_sprite_pattern(24+<a>,192+<b>)
%move_16x16_sprite_pattern(32+<a>,256+<b>)
%move_16x16_sprite_pattern(40+<a>,320+<b>)
%move_16x16_sprite_pattern(48+<a>,384+<b>)
%move_16x16_sprite_pattern(56+<a>,448+<b>)
endmacro








macro process_sprite(a,c,d)
lda $7f8000+<a>
sta $7f8400+<c>
lda $7f8002+<a>
cmp #$0100
bmi ?off_screen
lda #$0000
?off_screen:
!sub #$0020
sta $7f8401+<c>
lda $7f8006+<a>
and #$fe00
ora #$0000+<d>
sta $7f8402+<c>
endmacro


macro process_sprite2(a,c)
lda $7f8000+<a>
sta $7f8400+<c>
lda $7f8002+<a>
cmp #$0100
bmi ?off_screen
lda #$0000
?off_screen:
!sub #$0020
sta $7f8401+<c>
lda $7f8006+<a>
sta $7f8402+<c>
endmacro

macro move_16x16_sprite_pattern(a,b)
lda $7e0042
bne ?over
jmp last_sprite
?over:
dec
sta $7e0042
lda $7f8005+<a>
pha
plb
plb
lda $7f8004+<a>
tay
ldx #$0000+<b>
jsr move_sprite_pattern
endmacro

macro move_sprite_pattern(a)
lda $0000+<a>,y
sta $7f0000+<a>,x
lda $0002+<a>,y
sta $7f0002+<a>,x
lda $0004+<a>,y
sta $7f0004+<a>,x
lda $0006+<a>,y
sta $7f0006+<a>,x
lda $0008+<a>,y
sta $7f0008+<a>,x
lda $000a+<a>,y
sta $7f000a+<a>,x
lda $000c+<a>,y
sta $7f000c+<a>,x
lda $000e+<a>,y
sta $7f000e+<a>,x
lda $0010+<a>,y
sta $7f0010+<a>,x
lda $0012+<a>,y
sta $7f0012+<a>,x
lda $0014+<a>,y
sta $7f0014+<a>,x
lda $0016+<a>,y
sta $7f0016+<a>,x
lda $0018+<a>,y
sta $7f0018+<a>,x
lda $001a+<a>,y
sta $7f001a+<a>,x
lda $001c+<a>,y
sta $7f001c+<a>,x
lda $001e+<a>,y
sta $7f001e+<a>,x
endmacro



rep #$30

lda $0700         ;;counts to 4 and loops
inc
and #$0003
sta $0700




%process_row_sprites(0,0,256)
%process_row_sprites(64,32,288)
%process_row_sprites(128,64,320)
%process_row_sprites(192,96,352)
%process_row_sprites(256,128,384)
%process_row_sprites(320,160,416)
%process_row_sprites(384,192,448)
%process_row_sprites(448,224,480)


%process_row_sprites2(512,256)
%process_row_sprites2(576,288)
%process_row_sprites2(640,320)
%process_row_sprites2(704,352)
%process_row_sprites2(768,384)
%process_row_sprites2(832,416)
%process_row_sprites2(896,448)
%process_row_sprites2(960,480)




sep #$20         ;;this part does last 32 bytes of oam

lda $7f8019
asl
asl
ora $7f8011
asl
asl
ora $7f8009
asl
asl
ora $7f8001
sta $7f8600

lda $7f8039
asl
asl
ora $7f8031
asl
asl
ora $7f8029
asl
asl
ora $7f8021
sta $7f8601

lda $7f8059
asl
asl
ora $7f8051
asl
asl
ora $7f8049
asl
asl
ora $7f8041
sta $7f8602

lda $7f8079
asl
asl
ora $7f8071
asl
asl
ora $7f8069
asl
asl
ora $7f8061
sta $7f8603

lda $7f8099
asl
asl
ora $7f8091
asl
asl
ora $7f8089
asl
asl
ora $7f8081
sta $7f8604

lda $7f80b9
asl
asl
ora $7f80b1
asl
asl
ora $7f80a9
asl
asl
ora $7f80a1
sta $7f8605

lda $7f80d9
asl
asl
ora $7f80d1
asl
asl
ora $7f80c9
asl
asl
ora $7f80c1
sta $7f8606

lda $7f80f9
asl
asl
ora $7f80f1
asl
asl
ora $7f80e9
asl
asl
ora $7f80e1
sta $7f8607

lda $7f8119
asl
asl
ora $7f8111
asl
asl
ora $7f8109
asl
asl
ora $7f8101
sta $7f8608

lda $7f8139
asl
asl
ora $7f8131
asl
asl
ora $7f8129
asl
asl
ora $7f8121
sta $7f8609

lda $7f8159
asl
asl
ora $7f8151
asl
asl
ora $7f8149
asl
asl
ora $7f8141
sta $7f860a

lda $7f8179
asl
asl
ora $7f8171
asl
asl
ora $7f8169
asl
asl
ora $7f8161
sta $7f860b

lda $7f8199
asl
asl
ora $7f8191
asl
asl
ora $7f8189
asl
asl
ora $7f8181
sta $7f860c

lda $7f81b9
asl
asl
ora $7f81b1
asl
asl
ora $7f81a9
asl
asl
ora $7f81a1
sta $7f860d

lda $7f81d9
asl
asl
ora $7f81d1
asl
asl
ora $7f81c9
asl
asl
ora $7f81c1
sta $7f860e

lda $7f81f9
asl
asl
ora $7f81f1
asl
asl
ora $7f81e9
asl
asl
ora $7f81e1
sta $7f860f





lda $7f8219
asl
asl
ora $7f8211
asl
asl
ora $7f8209
asl
asl
ora $7f8201
sta $7f8610

lda $7f8239
asl
asl
ora $7f8231
asl
asl
ora $7f8229
asl
asl
ora $7f8221
sta $7f8611

lda $7f8259
asl
asl
ora $7f8251
asl
asl
ora $7f8249
asl
asl
ora $7f8241
sta $7f8612

lda $7f8279
asl
asl
ora $7f8271
asl
asl
ora $7f8269
asl
asl
ora $7f8261
sta $7f8613

lda $7f8299
asl
asl
ora $7f8291
asl
asl
ora $7f8289
asl
asl
ora $7f8281
sta $7f8614

lda $7f82b9
asl
asl
ora $7f82b1
asl
asl
ora $7f82a9
asl
asl
ora $7f82a1
sta $7f8615

lda $7f82d9
asl
asl
ora $7f82d1
asl
asl
ora $7f82c9
asl
asl
ora $7f82c1
sta $7f8616

lda $7f82f9
asl
asl
ora $7f82f1
asl
asl
ora $7f82e9
asl
asl
ora $7f82e1
sta $7f8617

lda $7f8319
asl
asl
ora $7f8311
asl
asl
ora $7f8309
asl
asl
ora $7f8301
sta $7f8618

lda $7f8339
asl
asl
ora $7f8331
asl
asl
ora $7f8329
asl
asl
ora $7f8321
sta $7f8619

lda $7f8359
asl
asl
ora $7f8351
asl
asl
ora $7f8349
asl
asl
ora $7f8341
sta $7f861a

lda $7f8379
asl
asl
ora $7f8371
asl
asl
ora $7f8369
asl
asl
ora $7f8361
sta $7f861b

lda $7f8399
asl
asl
ora $7f8391
asl
asl
ora $7f8389
asl
asl
ora $7f8381
sta $7f861c

lda $7f83b9
asl
asl
ora $7f83b1
asl
asl
ora $7f83a9
asl
asl
ora $7f83a1
sta $7f861d

lda $7f83d9
asl
asl
ora $7f83d1
asl
asl
ora $7f83c9
asl
asl
ora $7f83c1
sta $7f861e

lda $7f83f9
asl
asl
ora $7f83f1
asl
asl
ora $7f83e9
asl
asl
ora $7f83e1
sta $7f861f





rep #$30

ldx $0040            ;;$0040 is sprite oam counter
I_love_bratwurst:
lda #$0000
sta $7f8802,x
txa
!add #$0008
tax
cpx #$0400
bmi I_love_bratwurst

lda #$7f00
pha
plb
plb
lda #$0000
sta $7e0044
sta $7e0046
tax
sprite_organizer:         ;;this part of code separates
txa               ;;dynamic sprites from
cmp $7e0040            ;;standard sprites
beq sprite_organizer_done
cpx #$0400
beq sprite_organizer_done
lda $8804,x
beq standard_sprite
lda $7e0044
cmp #$0200
bpl dynamic_sprite
tay
lda $8800,x            ;;$7f8800 is sprite table
sta $8000,y            ;;$7f8000 is dynamic sprite table
lda $8802,x
sta $8002,y
lda $8804,x
sta $8004,y
lda $8806,x
sta $8006,y
tya
!add #$0008
sta $7e0044
bra dynamic_sprite
standard_sprite:
lda $7e0046
cmp #$0200
bpl dynamic_sprite
tay
lda $8800,x
sta $8200,y            ;;$7f8200 is standard sprite table
lda $8802,x
sta $8202,y
lda $8804,x
sta $8204,y
lda $8806,x
sta $8206,y
tya
!add #$0008
sta $7e0046
dynamic_sprite:
txa
!add #$0008
tax
bra sprite_organizer
sprite_organizer_done:
lda #$8000
pha
plb
plb

lda $0044
tax
clear_oam:
lda #$0000
sta $7f8002,x
txa
!add #$0008
tax
cpx #$0200
bmi clear_oam


lda $0046
tax
clear_oam_again:
lda #$0000
sta $7f8202,x
txa
!add #$0008
tax
cpx #$0200
bmi clear_oam_again

lda $0700
and #$0001
beq im_an_idiot
jmp odd_frame
im_an_idiot:


lda $0044
lsr #3
sta $0042


%move_row_sprite_patterns(0,0)
%move_row_sprite_patterns(64,1024)
%move_row_sprite_patterns(128,2048)
%move_row_sprite_patterns(192,3072)

jmp last_sprite
odd_frame:

lda $0044
lsr #3
!sub #$0020
sta $0042
bpl more_than_32_sprites
jmp last_sprite
more_than_32_sprites:

%move_row_sprite_patterns(256,4096)
%move_row_sprite_patterns(320,5120)
%move_row_sprite_patterns(384,6144)
%move_row_sprite_patterns(448,7168)


last_sprite:

lda #$8000
pha
plb
plb

jmp jump_over_this
move_sprite_pattern:
%move_sprite_pattern(0)
%move_sprite_pattern(32)
%move_sprite_pattern(512)
%move_sprite_pattern(544)
rts
jump_over_this: