tutorial · commodore 64
How the road scrolls
In Ruote Fumanti the road slides down from top to bottom, pixel by pixel, fifty times a second, and it never shakes. It looks like the simplest thing in the game. On a Commodore 64 it's the one that took the most ingenuity, because the machine has nothing ready-made for it: no video memory to draw into freely, no command that "moves the picture". This is how you get there, with the real pieces of the program.
the road at speed, recorded in VICE at fifty frames a second.
The code is 6510 assembly, the C64 processor's language, and you don't need to be able to read it: every piece is explained line by line right after. The comments inside the code, after the semicolon, are the originals, partly in English and partly in Italian, as I wrote them.
The C64 doesn't draw pixels: it lays tiles
The screen is a grid of 40 columns by 25 rows. Each cell holds not a picture but the number of a character: a little 8 by 8 pixel drawing taken from a table of 256. The road, the grass, the kerbs and the water are all characters redrawn for the purpose.
It's a very fast system, because changing what you see only takes writing a number into a cell. But for a driving game it has a big flaw: cells are fixed. A character sits in row 5 or row 6, not in between. If the road only moved by whole cells it would jump 8 pixels at a time, and at low speed that would be plain to see.
The seven-pixel trick
The video chip, the VIC-II, has a register called YSCROLL: it runs from 0 to 7 and shifts the whole picture down by that many pixels. So scrolling happens in two beats. For the first seven pixels no cell is touched: YSCROLL goes up and the entire screen slides down. When you'd reach the eighth, you set YSCROLL back by 8 and at the same instant copy the whole road one row lower, putting a new row at the top.
eight frames: in the first seven only a register changes, on the eighth the road is copied one row down and the register goes back to zero. The row that dropped a cell climbs back eight pixels, and the eye sees nothing.
Whoever's watching doesn't see the jump, because the two things cancel out exactly. Two problems remain, and the rest of the tutorial is their solution. First: copying the road takes time, and during the copy the screen would be half old and half new. Second: with YSCROLL at 7, a band of empty pixels would show at the top. That one is solved at once, because the C64 has a 24-row mode instead of 25, which covers half a row at the top and half at the bottom with the border: the incoming row stays hidden behind the border.
How far it scrolls in a frame
First of all you need to know how many pixels to move the road down, and that depends on speed.
The C64's processor can't multiply: it can only double a number (asl, "shift left") and add (adc). So "six times the speed" becomes twice plus four times. lda fis_speed_hi reads the speed in miles per hour; the two asl tmp / rol tmp2 double it and double it again, and the "twice" value is set aside in count; the two additions put twice and four times together.
The result isn't in pixels but in 256ths of a pixel: the high byte, step_px, is whole pixels, the low byte is the fraction. And the fraction isn't thrown away: it's added to fis_frac, which keeps it from frame to frame, and when the accumulated fractions make a whole pixel that pixel gets added. That's what keeps the scroll even at "odd" speeds: at 50 mph the road moves 1.17 pixels a frame, which is usually 1 and about one frame in six 2, always at the same cadence. Without the accumulator it would move 1 pixel and that's it, and 50 or 80 would look the same.
Seven pixels, then the copy
Now that we know how far to move, this is the piece that decides whether YSCROLL is enough or a copy is needed.
lda nextfine / adc step_px takes the current fine position, the YSCROLL value from 0 to 7, and adds this frame's pixels. cmp #8 / bcs .coarse: if the total reaches 8 or more a copy is needed, otherwise sta nextfine saves the new YSCROLL and rts returns. Most frames end right here: seven instructions, and the road has already moved.
Under .coarse you count how many whole rows have gone by, one, or two if the total reaches 16; while racing, even at 220 mph, pixels per frame top out at six, so normally it's a single row. and #7 is the "set YSCROLL back by 8": it keeps only the remainder, so if you were at 6 and add 5 the total is 11, one row is copied and YSCROLL becomes 3. lda front / eor #1 / sta nextfront is the trick in the next chapter: front says which of the two screens is being shown, and eor #1 picks the other.
Two screens, like a flip book
The half-copied screen problem is solved like this: there are two screens. The C64 lets you tell the video chip which area of memory the screen lives in, and Ruote Fumanti keeps two, one at $4000 and one at $4400. One is visible, the other hidden. The program always writes into the hidden one, taking its time, and when the drawing is done it tells the video chip to show the other. It's the flip-book principle: you don't rub out the page you're looking at, you prepare the next one and then turn the page.
This isn't code that runs: it's a recipe for the assembler, the program that turns assembly into machine code. !for means "repeat": for each of the 24 rows and each of the 25 road columns, write two instructions. lda $4000+... reads the character in row .r of screen A, sta $4400+(.r+1)*40... writes it into screen B one row lower. PANEL_COLUMNS skips the first eleven columns, where the panel sits, which doesn't scroll.
The result is 600 pairs of instructions one after another, with no loop at all. Today you'd write a loop with a counter, much shorter. Here, though, time matters, not length: on the 6510 moving a byte with indexed addressing costs 9 processor cycles, against 8 with the address spelled out, and the loop costs more on top. The original comment in the source puts it like this: "The scroll copy, unrolled. Indexed addressing costs nine cycles a byte and a loop on top; plain absolute costs eight and nothing on top."
In numbers: 600 characters at 8 cycles make 4800 cycles. A PAL frame has 19656, that is 312 video lines at 63 cycles each, so the copy alone takes almost a quarter of the frame. At full speed, with just over five pixels a frame, it happens about two frames in three.
One thing the copy does not do: it doesn't copy colours. On the C64 colours live in separate memory, and there's no spare screen there. That's why the road is drawn with just four colours, and each cell's own colour is the same everywhere: that way colours don't need moving, and they scroll for free. The only exception is the chequered finish flag, which has its own little piece of code for the black and white.
The track doesn't exist, it's composed
After the copy, a row is missing at the top of the hidden screen. A stage is 2304 rows long, that's 18432 pixels, and keeping it in memory already drawn would cost too much: a single track took ten kilobytes, and there are eight stages. So the road is composed on the spot, the way arcade cabinets did it.
It works like a three-level mosaic. The stage map is a list of blocks: each block is a 32-row piece of road, with a number saying what shape it has, straight, narrow, with a ford. Each block is a grid of tiles two characters wide and two high. Each tile says which four characters to draw. The last line of the comment is the formula for finding everything from the row number: block = q>>5 means "divide by 32", (q>>1)&15 finds the tile row inside the block, and q&1 says whether you're drawing the top or bottom half of the tile. Bit shifts in place of divisions again, because the processor can't divide.
So all eight stages cost a little 128-byte table plus this routine. And there's an advantage that has nothing to do with space: the physics reads the road from the same characters drawn on the screen. What you see under your wheels is exactly what you're driving on, because it's the same thing. The piece that calls the routine, right after the copy:
screen_row_lo / screen_row_hi are a table holding the address of each screen row, so there's no need to multiply by 40. ldx nextfront / adc #4: if the destination screen is the second one, its address is four "pages" further on, $4400 instead of $4000. jsr decode_row composes the row and writes it there, and dec native_rows_due / bne .incoming repeats if there were two rows to compose.
Turning the page at the right moment
The hidden screen now holds the new frame, and there's the new YSCROLL value. The most delicate part remains: showing them together, at a moment when the video chip isn't drawing. The VIC-II draws the picture top to bottom, one line at a time: switch screens halfway and the top comes from the old one and the bottom from the new, and you see a tear. To avoid it the C64 can ask the video chip to interrupt the program when it reaches a given line: these are raster interrupts.
a PAL frame laid out horizontally, from line 0 to line 311. Work starts right after delivery, in the bottom border, and normally finishes halfway through the visible area.
Ruote Fumanti uses an interrupt at line 304, below the visible area, where the chip is drawing the border. That's where the frame is delivered.
lda ready / beq .miss: the main program raises ready only once it has finished all of the frame's work. If it hasn't, nothing is delivered, the previous frame is kept and one frame is counted as dropped. Better to repeat a whole picture than to show half of one. lda nextfront / sta front: the prepared screen becomes the one to show. The four asl and the ora #8 build the value for the $d018 register, whose upper half tells the chip where the screen is and whose lower half where the characters are: with front at 0 you get $08, screen at $4000; with front at 1 you get $18, screen at $4400. lda nextfine / sta fine: the new YSCROLL.
The values actually reach the chip a moment later, at the top of the next frame, with another interrupt:
lda fine / ora #$10 / sta $d011: a single register holds YSCROLL, in the three low bits, and the video settings. It's worth being precise here, because this register confuses everyone: the bit that ora #$10 sets is bit four, and it means "display on"; the 24-row mode is the bit just below, bit three, and you get it by leaving it at zero, which is exactly what writing $10 instead of the usual $18 does. The original comment describes the effect: it's the 24-row window that hides the incoming row, and that's what makes the scroll seamless. sta $d018 is the screen switch. It all happens in the border, before the chip starts drawing the road: whoever's watching always sees a complete picture.
Forty free lines
There's a detail in the delivery worth telling, because it was found by measuring, not by reasoning.
tick is the go signal for the main program: "start the next frame". At first the go was given at the top of the picture, at line 32. But delivery happens at line 304, and between 304 and 32, going round the bottom and the top border, there are forty lines in which the processor sat waiting. They're precious lines, because while the video chip draws the visible area it now and then "steals" time from the processor to read the characters, whereas in the border it steals nothing.
Moving those two instructions, lda #1 and sta tick, from line 32 to right after delivery gave the work about 2500 more cycles every frame. Measured over all eight stages with fixed inputs, dropped frames went from 1.93% to 0.11%. Two instructions moved, and the game stopped stuttering.
moral: no amount of reasoning would have found those forty lines. A number found them, measured the same way every time, before and after.
Why the panel doesn't jiggle
YSCROLL moves the whole screen, not just the road. If score, speed and time were written with characters, they'd bob up and down seven pixels along with the road. That's why the panel's lettering isn't characters but sprites, which YSCROLL doesn't touch.
Except there are only eight sprites, and the cars need them too: the panel uses just two. Each time the video chip reaches a new band of the panel, another interrupt moves those two sprites further down and changes the digits they show. The panel background, on the other hand, is made of characters, but chosen on purpose, solid or with vertical stripes for the frames: they're identical on every row, so when they drop a few pixels nobody notices.
One frame of Ruote Fumanti
- line 304, bottom border if it's ready, deliver: new screen and YSCROLL, and go straight to work
- main program physics, and from speed the pixels to scroll, fractions included
- under eight pixels just update YSCROLL
- eight or more copy into the hidden screen and compose a new top row
- line 32, at the top the chip gets screen and YSCROLL, before drawing the road
- while drawing panel interrupts move the two lettering sprites
Measured in VICE with fixed inputs over 9600 frames and eight stages: 0.1% dropped frames, and six stages out of eight that don't drop a single one.