re: [stella] How RRampage works

Subject: re: [stella] How RRampage works
From: "Lee Fastenau" <stella@xxxxxxxxxxxxxxx>
Date: Mon, 12 Apr 2004 12:02:10 -0700
>I'm bored at work and can't stop thinking about River Rampage.  So I wrote
>up a description of how it works. :)

Yeah, I don't think anyone wanted to pry, but THANK YOU. :)

>First, player 1 and missile 1 are used to draw the player and his bullets;
>P0 and M0 are used for the enemies and their shots.  This is because P0/M0
>have higher display priority than P1/M1; you don't want your bullets to
>obscure things that you need to dodge.

But wouldn't you (and your bullets) be flying more or less "over" objects you need to dodge?

>The kernel is basically a 4-line loop (it's actually unrolled to 8, but 4
>is good enough to describe it):

>Line 1: reposition P0 if necessary
>Line 2: do a bunch of calculations
>Line 3: reposition M0 (this always happens every 4 lines)
>Line 4: on every other iteration, HMOVE and display the player's bullets
>(so every bullet is 8 scanlines tall)

So all bullets have a vertical resolution of 4 pixels, but are 8 pixels tall?

>NUSIZ1 is set to draw the multiple copies of the bullets, of course.  The
>trick is that P1 and M1 are never fully repositioned during the screen -
>they're only HMOVEd, so we can reposition from one bullet to the next
>using only a few instructions instead of an entire scanline for each
>object.  But the bullets are independent - what if you shoot, stop
>shooting, and shoot again so that there's a large horizontal space between
>successive bullets?

This requires, what, 24 bytes of RAM (or slightly less) for P1's bullets?  And probably the same for P0's?

>The answer is that the game keeps calculating and HMOVEing invisible
>bullets on all the inbetween lines, even when you're not shooting, using
>GRP1 and ENAM1 to turn them off when appropriate.  It initializes a new
>bullet every frame whether or not the joystick button is pressed.


>As for P0, I actually only reposition it every *four* lines, yet it still
>has single-line resolution.  That's done through the indirect-indexed
>pointer to the sprite graphics.  Every scanline does an indirect-indexed
>graphics lookup and store to GRP0.  Every enemy sprite image in ROM is
>padded with three extra bytes of zeroes; and if necessary the code can
>adjust the pointer by up to 3 bytes.[1]  So after the repositioning, we
>can keep writing 0 to GRP0 for up to three additional lines until it
>really is time to display the object.  Similar idea to the hardware VDEL
>registers, but much more flexible and powerful.

Right now, and this is embarassing to admit, that tiny 3x6 pixel ball eats up 512 bytes (1/8 of my ROM) because I rely on the blank space for positioning.  I've restructured my kernel a number of times, but have yet to tackle good ball-rendering optimization.  If I can live without the ROM space, I may leave it as is.  In fact, with the current size of my kernel, endeavoring to reduce the space the ball itself takes up may increase the kernel size by nearly that much.

>To make the GRP0 handling as fast as possible, I'm not even using
>SkipDraw.  When the kernel reaches the end of a player object (this check
>runs once every 4 lines), it changes the high byte of the 16-bit pointer
>to point to an entire ROM page full of just zeroes.  Then all the GRP0
>lookups will just grab zeroes until we reach and initialize a new enemy
>object while scanning down the screen.  ROM is plentiful, cycles aren't.
>:)  This results in being able to have any vertical distance between two
>enemies, instead of constricting them to a specific schedule like in River

Heh, I think that's exactly what I was describing.  Maybe I wasn't so crazy after all! :)

>The laser is the ball.  That's just positioned once, offscreen, and when
>the kernel gets to the player's sprite, it writes 0 to ENABL.  Cake.
>The last trick is how P1 gets repositioned after the bullets to draw the
>player's plane, without any interruption to the rest of the repositioning.
>This is done in a way that I theorized about years ago. :)  When we reach
>the player's plane, the player's bullets are no longer being drawn, so
>scanline 4 of the kernel loop is available.  It uses a JMP Indirect to
>branch to one of 11 different versions of that scanline based on the
>horizontal position of the player.  Each version has its instructions
>ordered so that a STA RESP1 is timed to hit at the appropriate time.  That
>does the coarse horizontal positioning just as a regular delay loop would,
>and HMP1/HMOVE fine-tunes the position as usual, and lets it run right
>back into positioning P0 on the next scanline if necessary.
>[1]This isn't implemented just yet (which is why that demo doesn't move
>anything vertically), as I need to work out exactly how to allocate the
>Superchip RAM.  It'll be easiest if I have enough RAM to set up
>(offscreen) a separate pointer for each enemy.  Gotta find some time to do
>more coding this week...

So this is a Supercharger-only endeavor?  Does this current incarnation require a Supercharger?

Thanks for the terrific breakdown!  :)

Archives (includes files) at
Unsub & more at

Current Thread