
On the TC-01 everything that moved was a sprite. The video chip drew sprites over the screen for you, so a moving ship never left a mess, and S22's animation was a matter of changing one byte. Anything drawn into the screen itself had to be rubbed out and drawn again, while the picture was being shown.
The 256's bitmap modes invite you to draw much more than 32 sprites' worth: whole ships, explosions, scenery, in sixteen colours at every dot. Draw them on the screen the player is watching, and the player sees them half-drawn. This section draws them where the player can't see.
In S5 and S11 you saw that what the CPU writes (register 14) and what the screen shows (register 2) are separate. In Graphics 4 there are four pages. Take two:
| Shown by R#2 = | Written with R#14 = | |
|---|---|---|
| page A, 10000h | 5Fh | 4, 5 |
| page B, 18000h | 7Fh | 6, 7 |
While page A is on screen you draw the next picture on page B. When it is finished you write 7Fh to register 2, and page B appears complete. Then you draw on page A, and so on. The player only ever sees finished pictures.
Both pages are above 10000h, so MOS's screen is safe, and the sprite table from S11 sits in page B's unseen lines at 1FA00h.
A change to register 2 takes effect from the next line the chip draws. Write it while the picture is half shown and the top of the screen comes from one page and the bottom from the other, for one frame.
So flip from the frame interrupt. The main loop finishes a page, sets a flag and waits. onframe (S7) sees the flag, writes register 2 and clears the flag. The switch lands between two pictures, never in the middle of one:
onframe:
LD A, (flip)
OR A
RET Z ; nothing new: keep showing the same page
LD A, (draw)
LD (shown), A
OR A
LD A, 5Fh ; page A
JR Z, show
LD A, 7Fh ; page B
show: LD B, 2
CALL setreg ; R#2: the finished page
XOR A
LD (flip), A ; done: the main loop may carry on
RET
No MOS call in sight, as S7 requires.
Page flipping has two rules, and breaking either one brings back the mess you were avoiding.
Never draw on a page until the flip that hid it has happened. After asking for a flip, the main loop waits for onframe to clear the flag. Until then the page it just drew is not yet on screen, and the other one still is. Start drawing at once and you are drawing on the page the player can see.
Rub out where this page last had the object. Each page has its own copy of the picture. When you come back to page A, it holds the ship where you drew it on page A, two frames ago, not where page B has it now. So keep one old position for each page:
pos: DEFB 0,0, 0,0 ; where each page has the ship: x, y for A, then B
Rub out at this page's entry, move, draw, and store the new position in the same entry. Use one position for both and each page rubs out the wrong place: the ship leaves a trail.
A ship drawn into the bitmap, bouncing round a night sky above the ground, two dots across and one line down every frame. Its picture is held one hex digit per dot, so you can see it in the source: F white, 7 cyan, E grey, 8 and 9 reds, 1 the black sky. The rub-out is the same rectangle of sky. After 250 frames, MOS gets its screen back.
; S13: flicker-free movement in Graphics 4 by page flipping.
;
; Two Graphics 4 pages, both above MOS's screen:
; page A at 10000h: shown by R#2 = 5Fh, drawn with R#14 = 4 and 5
; page B at 18000h: shown by R#2 = 7Fh, drawn with R#14 = 6 and 7
; The player always sees one page while the program draws on the other.
; Each pass of the main loop, on the hidden page:
; 1. rub the ship out where THIS page last had it
; 2. move the ship
; 3. draw it at the new place, and remember the place for this page
; 4. ask for a flip, and wait until the frame interrupt has done it
; The frame interrupt (frame.asm, S7) shows the finished page with R#2.
; After 250 frames (5 seconds) MOS gets its screen back.
ORG 256
DI
; --- Graphics 4 first, so writes run on past each 16K
LD A, 06h
LD B, 0
CALL setreg ; R#0 = 06h: Graphics 4
; --- the sprites' own attribute table at 1FA00h: only Y = 216
LD A, 7
LD B, 14
CALL setreg
XOR A
OUT (9), A
LD A, 3Ah + 40h
OUT (9), A
LD A, 216
OUT (8), A
LD A, 0F7h
LD B, 5
CALL setreg ; R#5 = F7h
LD A, 03h
LD B, 11
CALL setreg ; R#11 = 03h
; --- the same background on both pages: black, with ground
LD A, 4 ; page A
CALL scene
LD A, 6 ; page B
CALL scene
LD A, 5Fh
LD B, 2
CALL setreg ; R#2 = 5Fh: show page A
CALL irqon
LD A, 0E0h
LD B, 1
CALL setreg ; display on, frame interrupt on
EI
; --- the main loop
main: LD A, (shown)
XOR 1
LD (draw), A ; draw on the page that is not shown
; 1. rub out the ship where this page last had it
CALL oldpos ; HL -> this page's saved position (x, y)
LD E, (HL)
INC HL
LD D, (HL)
LD HL, blank
CALL put
; 2. move it
CALL move ; E = new x (bytes), D = new y (lines)
; 3. draw it there, and remember where, for this page
PUSH DE
CALL oldpos
POP DE
LD (HL), E
INC HL
LD (HL), D
LD HL, ship
CALL put
; 4. ask for the flip, and wait for it
LD A, 1
LD (flip), A
wait: LD A, (flip)
OR A
JR NZ, wait ; never touch a page until the flip has hidden it
LD HL, (TICKS)
LD A, H
OR A
JR NZ, done
LD A, L
CP 250
JR C, main
done: LD A, 0C0h
CALL irqoff ; frame interrupt off, MOS's table back
CALL tomos ; MOS's screen back
RET
; --- onframe: called every frame. If a page is finished, show it.
onframe:
LD A, (flip)
OR A
RET Z ; nothing new: keep showing the same page
LD A, (draw)
LD (shown), A
OR A
LD A, 5Fh ; page A
JR Z, show
LD A, 7Fh ; page B
show: LD B, 2
CALL setreg ; R#2: the finished page
XOR A
LD (flip), A ; done: the main loop may carry on
RET
; --- oldpos: HL -> the saved (x, y) for page (draw)
oldpos: LD A, (draw)
ADD A, A
LD E, A
LD D, 0
LD HL, pos
ADD HL, DE
RET
; --- move: step x by dx (one byte = 2 dots) and y by dy, bouncing.
; Returns E = x, D = y.
move: LD A, (x)
LD B, A
LD A, (dx)
ADD A, B
LD (x), A
OR A
JR Z, mx
CP 120 ; 128 bytes across, less the ship's 8
JR NZ, my
mx: LD A, (dx)
NEG
LD (dx), A
my: LD A, (y)
LD B, A
LD A, (dy)
ADD A, B
LD (y), A
OR A
JR Z, mz
CP 160 ; the ship's 16 lines end at the ground (176)
JR NZ, mdone
mz: LD A, (dy)
NEG
LD (dy), A
mdone: LD A, (x)
LD E, A
LD A, (y)
LD D, A
RET
; --- put: copy 16 lines of 8 bytes from (HL) to byte x = E, line y = D
; of page (draw)
put: LD A, (draw)
ADD A, A
ADD A, 4 ; R#14 base: 4 for page A, 6 for page B
LD C, A
LD B, 16 ; 16 lines
put1: PUSH BC
CALL setw
LD B, 8 ; 8 bytes = 16 dots
put2: LD A, (HL)
OUT (8), A
INC HL
DJNZ put2
INC D ; next line
POP BC
DJNZ put1
RET
; --- setw: write address for byte E of line D, page base C.
; y*128 + x = (y/2)*256 + (y's bit 0)*128 + x
setw: PUSH HL
LD A, D
SRL A
LD H, A ; H = y/2, carry = y's bit 0
LD A, 0
RRA
OR E
LD L, A ; HL = y*128 + x
DI ; no interrupt between the bytes of an address
LD A, H
RLCA
RLCA
AND 3
ADD A, C ; which 16K of the page
OUT (9), A
LD A, 80h + 14
OUT (9), A ; R#14
LD A, L
OUT (9), A
LD A, H
AND 3Fh
OR 40h
OUT (9), A ; address, for writing
EI
POP HL
RET
; --- scene: clear the page whose R#14 base is A to black (192 lines),
; then lay the ground on lines 176-191
scene: LD B, 14
CALL setreg
XOR A
OUT (9), A
LD A, 40h
OUT (9), A ; the page's first byte, for writing
LD D, 176 ; 176 black lines: 176 x 128 bytes
LD A, 11h
CALL rows
LD D, 1 ; one line of grass
LD A, 22h
CALL rows
LD D, 15 ; and earth to line 191
LD A, 0CCh
CALL rows
RET
; --- rows: D lines of byte A, carrying on from the current address
rows: LD B, 128
rw1: OUT (8), A
DJNZ rw1
DEC D
JR NZ, rows
RET
; --- the ship, one hex digit a dot: colour 1 is the black sky
ship: DEFB 011h,011h,011h,01Fh,0F1h,011h,011h,011h
DEFB 011h,011h,011h,0FFh,0FFh,011h,011h,011h
DEFB 011h,011h,011h,0F7h,07Fh,011h,011h,011h
DEFB 011h,011h,01Fh,0F7h,07Fh,0F1h,011h,011h
DEFB 011h,011h,01Fh,077h,077h,0F1h,011h,011h
DEFB 011h,011h,0FFh,077h,077h,0FFh,011h,011h
DEFB 011h,011h,0FEh,0EEh,0EEh,0EFh,011h,011h
DEFB 011h,01Fh,0FEh,0EEh,0EEh,0EFh,0F1h,011h
DEFB 011h,0FFh,0FFh,0EEh,0EEh,0FFh,0FFh,011h
DEFB 01Fh,0FFh,0FFh,0FFh,0FFh,0FFh,0FFh,0F1h
DEFB 0FFh,0F1h,01Fh,0FFh,0FFh,0F1h,01Fh,0FFh
DEFB 0F1h,011h,01Fh,0FFh,0FFh,0F1h,011h,01Fh
DEFB 011h,011h,01Fh,088h,088h,0F1h,011h,011h
DEFB 011h,011h,011h,089h,098h,011h,011h,011h
DEFB 011h,011h,011h,019h,091h,011h,011h,011h
DEFB 011h,011h,011h,011h,011h,011h,011h,011h
; --- the rub-out: the same 16 x 8 bytes, all sky
blank: DEFS 128, 11h
shown: DEFB 0 ; the page on screen: 0 = A, 1 = B
draw: DEFB 0 ; the page being drawn
flip: DEFB 0 ; 1 = show the drawn page at the next frame
pos: DEFB 0,0, 0,0 ; where each page has the ship: x, y for A, then B
x: DEFB 0
y: DEFB 0
dx: DEFB 1
dy: DEFB 1
INCLUDE "frame.asm"
Build and run
Save it as s13-flicker-free.asm next to frame.asm. Build and run it as in S2.
What you should see
A black sky over a strip of green ground, and a small white ship with a cyan canopy and a red flame. It flies diagonally down and to the right, two dots across and one line down every frame, and bounces off the edges. In every single frame the ship is whole: never half-drawn, never doubled, and it leaves nothing behind.

After five seconds MOS's screen comes back, cleared, with the register display and the prompt.
oldpos, replace LD A, (draw) with XOR A, so both pages share page A's old position. Before you run it: what will be left behind, and how many trails will there be?XOR 1 with NOP, so the program always draws on the page being shown. What do you expect to see? Which of the two rules does it break?dx to 2. How far does the ship move each frame, and does the rub-out still work? Why?| Symptom | Cause |
|---|---|
| Two dotted trails follow the ship across the screen | Both pages rubbed out at one shared old position. Each page needs its own |

You can animate anything drawn into a bitmap without flicker: draw on the hidden page, flip with register 2 from the frame interrupt, wait for the flip before touching a page again, and rub out where that page last had the object. It costs a second page and a redraw on each.
S14, Scrolling the Screen: register 23 moves the whole picture, and a game only has to draw the lines that scroll into view.