Software Scrolling
When hardware won't help
The ZX Spectrum has no scroll hardware, so scrolling means moving screen bytes: character steps, one-pixel rotates, or the stack-pointer "push-scroll" Software Creations used for Ghosts 'n' Goblins. The choice shaped how Spectrum games moved.
The ZX Spectrum has no hardware scroll registers. Moving the screen means moving every byte of it, and the cost shaped Spectrum game design: most games changed screen a whole screen at a time, and those that scrolled either moved a window, moved in character steps, or spent most of the machine on it.
The cost
The screen is 6,144 bytes of pixel data plus 768 bytes of attributes. Copying all of it once with LDIR:
6,144 bytes × 21 T-states per LDIR byte ≈ 129,024 T-states
At 3.5 MHz, that is ~37 ms — nearly two frames, before drawing anything
A full-screen copy every frame is out of reach; anything that scrolled had to do less than that.
Screen layout
The display file is not linear, and the manual says so: it “is rather curiously laid out, so you probably won’t want to PEEK or POKE in it”. The layout is in thirds of 64 lines, each third stored as “the top scans of lines 0 to 7, then the next scans of lines 0 to 7, and so on”:
Pixel rows 0-7 of character row 0: $4000, $4100, $4200, ... $4700
Pixel rows 0-7 of character row 1: $4020, $4120, $4220, ...
...
Character row 8 (second third): $4800, $4900, ...
The attributes, by contrast, “are stored line by line in the order you’d expect”: 768 bytes from $5800, 32 per row.
Two things follow for scrolling. Every 32-byte block in memory is one pixel row, so a horizontal scroll can walk the 6,144 bytes in memory order and never needs to know which row it is on. A vertical scroll has to move pixel row y to the address of row y−1, which jumps between thirds, character rows and scan lines; Andrew Hewson printed both kinds in Sinclair User in 1986 and noted that “the up and down routines are a lot longer than the left/right routines because the Spectrum screen is laid out in a seemingly” illogical way.
Address calculation
The formula: addr = $4000 | ((y & $C0) << 5) | ((y & $07) << 8) | ((y & $38) << 2). Y[7-6] selects the third (high byte +$00, +$08 or +$10). Y[2-0] picks the pixel row within a character row (high byte +$00 to +$07). Y[5-3] picks the character row within a third (low byte +$00 to +$E0 in steps of $20).
; Convert Y coordinate to screen address (column 0)
; Input: B = Y (0-191)
; Output: HL = screen address of byte 0 of that pixel row
y_to_address:
ld a, b
and %00000111 ; Y[2:0]
or $40 ; high byte base = $40
ld h, a ; H = $40 | Y[2:0]
ld a, b
and %11000000 ; Y[7:6]
rrca
rrca
rrca ; Y[7:6] >> 3 (puts bits in high byte's bits 4-3)
or h ; merge into high byte
ld h, a ; H = $40 | Y[2:0] | (Y[7:6] >> 3)
ld a, b
and %00111000 ; Y[5:3]
rlca
rlca ; Y[5:3] << 2 (gives byte offset 0, $20, $40, ..., $E0)
ld l, a ; L = column 0 of this pixel row
ret
Check: Y=0 → $4000; Y=8 → $4020; Y=64 → $4800; Y=191 → $57E0.
One-pixel horizontal scroll
Shifting the whole screen one pixel left means rotating each 32-byte row through the carry flag, right to left, so the bit that falls out of the top of byte n lands at the bottom of byte n−1:
scroll_left:
ld hl, $401F ; last byte of the first row in memory
ld de, 32
ld c, 192 ; 192 rows, in memory order
.row:
push hl
or a ; clear carry: zeros come in from the right
ld b, 32
.byte:
rl (hl) ; bit 7 → carry, carry → bit 0
dec hl
djnz .byte
pop hl
add hl, de ; next 32-byte row
dec c
jr nz, .row
ret
Walking the bytes from $401F downwards is what makes it a left scroll; walking upwards with the same rl moves the pixels right and back into the wrong byte. Unrolling the inner loop removes the DJNZ and DEC HL overhead at the cost of thirty-two rl (hl) per row.
rl (hl) costs 15 T-states, so a one-pixel shift of the full screen is around 92,000 T-states before any loop overhead — cheaper than a copy, but still most of a frame.
Character scroll
Moving whole characters is the cheap version. Each pixel row is 32 bytes, so an 8-pixel left scroll copies bytes 1 to 31 down to bytes 0 to 30 and refills byte 31:
; Scroll the whole screen left by 8 pixels, one pixel row at a time
scroll_char_left:
ld hl, $4001 ; source: one byte right of the destination
ld de, $4000 ; destination
ld a, 192 ; 192 pixel rows, in memory order
.row:
push af
ld bc, 31
ldir ; HL = $4020, DE = $401F
ex de, hl
ld (hl), 0 ; refill byte 31 — blank here, or the new column of the map
ex de, hl
inc hl ; HL = $4021: source for the next row
inc de ; DE = $4020: destination for the next row
pop af
dec a
jr nz, .row
ret
Because every 32-byte block is one pixel row, this linear walk is correct for the whole screen; the display-order interleave only matters for vertical movement. This is the “4-pixels-every-3-frames” family Software Creations described as the average Spectrum scroll: move in steps, and draw the new column into the freed edge.
Push-scroll
The technique the same team called “extremely unpleasant to even think about for too long” uses the stack pointer as a 16-bit copy engine. POP reads two bytes and advances SP in 10 T-states; PUSH writes two in 11. With SP pointed into the screen, the game can read a row of screen data two bytes at a time, shift and mask it in registers, and push it back — moving, scrolling and sprite-masking in one pass, with no LDIR and no per-byte address arithmetic. It costs the stack: interrupts must be off for the whole pass, and every register pair is spoken for. Software Creations’ account of Ghosts ‘n’ Goblins and Ghouls ‘n’ Ghosts describes “using the Z80 processor’s 16-bit register SP to do both the scrolling and masking the sprites over the background”, achieving “a 2-pixel horizontal scroll combined with a 1 pixel scroll … in less than 2 TV frames” — and paying for it with a processor “uninterruptably tied up for most of the 2-frame game cycle, so music could only be played at a 25Hz rate”.
The two-pixel step has a second cost. “Every sprite must then be printable at a 2-pixel resolution. On the Spectrum, this means having 4 shifted images of each sprite animation and background block in memory”: pre-shifted copies trade RAM for the shifts that would otherwise happen every frame.
Attributes
The attribute grid does not move in pixels:
| Scroll amount | Attribute handling |
|---|---|
| 8 pixels | Scroll the attribute file too: 768 bytes, in order |
| 1-7 pixels | Attributes cannot follow; the colour stays put while the pixels move |
So a pixel-scrolling game either scrolls a monochrome area, keeps colour to bands that do not need to move, or — as Software Creations put it — accepts that “the design of the backgrounds is severely limited by the scrolling technique”. The attribute file on its own, scrolled at 768 bytes a frame, was cheap enough to become a demo-scene fixture: Your Sinclair was reviewing “the obligatory large attribute scrolly” in 1993.
Window scrolling
Scrolling part of the screen scales the cost by the window. A window w characters wide on rows y0 to y1 is a horizontal scroll of w bytes per pixel row, with y_to_address (or a memory-order walk, if the window is full height) supplying each row’s start. Vortex’s H.A.T.E. moved its diagonal playfield two pixels at a time, and Costa Panayi’s account gives the scale of the effort: “we used 18K solely for the scrolling … it was the fifth re-write that finally gave us the speed we needed.”
Summary
| Approach | Cost | Motion |
|---|---|---|
| Flip-screen | One redraw per screen | Instant |
| Character scroll | 31 bytes copied per pixel row, plus a new column | 8-pixel steps |
Pixel scroll (rl) |
One rotate per byte | Smooth, one pixel |
| Push-scroll | One POP/PUSH pair per two bytes, interrupts off |
Smooth, any step |
| Window scroll | Proportional to window | Area-limited |
| Attribute-only | 768 bytes | Colour blocks only |
Who scrolled
- Blue Thunder (Richard Wilcox Software, 1984) — advertised as “Hi Res Graphics with Pixel Scrolling over 6 screens” in CRASH’s third issue, one of the earliest to sell on the word.
- Ghosts ‘n’ Goblins (Elite, 1986) and Ghouls ‘n’ Ghosts (US Gold, 1989) — Software Creations’ push-scroll, described above; Mike Follin programming.
- Uridium (Hewson, 1986) — Dominic Robinson’s conversion, whose smooth scrolling CRASH still called “almost unique” two years later.
- H.A.T.E. (Vortex/Gremlin, 1989) — Costa Panayi’s two-pixel scroll, five rewrites and 18K of code.