Skip to content

Linecrunch

Squeezing a character row into one raster line

A Commodore 64 trick that makes the VIC-II treat a whole character row as finished after a single raster line, so everything below it moves up the screen without a byte of memory being copied. It is FLD's partner in the opposite direction, it needs much tighter timing, and the scene's own list of firsts records it on a cracked game's intro in July 1987.

Linecrunch moves the Commodore 64’s picture up the screen without moving any of it. On the right cycle of each raster line, a program changes the vertical scroll bits of $D011, and the VIC-II draws one line of a character row, decides the row is finished and moves on to the next. Each row crunched this way takes one raster line instead of eight, so everything below it rises by seven. Crunch ten rows and the rest of the screen is seventy lines higher, and the processor has not copied a single byte.

That matters most for bitmaps. A text screen is 1,000 character codes; a bitmap picture is 8,000 bytes of pixels plus 1,000 of colour, which the Codebase64 wiki’s linecrunch page puts at “~9 times the data of a char screen”, far too much to copy every frame. Linecrunch is the upward partner of FLD, which pushes rows down, and with VSP for sideways movement the three let a program scroll a whole picture in any direction. Christian Bauer, whose 1996 description of the chip is the standard reference, put it this way: “you can scroll complete graphics screens without using much computing time by almost arbitrarily large distances in all directions.”

How it works

The VIC-II keeps its place in the picture with two counters. VC points into screen memory and moves on by 40 for each character row; RC counts the eight pixel lines within the row. Bauer sets out when they change:

  • On a badline, the first line of a row, the bottom three bits of the raster line match YSCROLL (bits 0 to 2 of $D011). In cycle 14 the chip resets RC to 0, and in cycles 15 to 54 it reads the row’s 40 character codes and colours, stopping the processor while it does.
  • In cycle 58 of every line the chip checks RC. If RC is 7, the row has had its eight lines: the chip goes into its idle state and records where the next row starts, 40 characters on.
  • The chip leaves the idle state “as soon as there is a Bad Line Condition”, at any cycle.

Linecrunch plays these rules against each other. The listing below writes $D011 just after the cycle-58 check, setting YSCROLL to match the line that is ending. That badline condition comes too late for any character fetches, but it puts the chip straight back into display state with RC still at 7. On the next line YSCROLL no longer matches, so there is no badline: RC is not reset and nothing new is fetched. The chip draws one line, the eighth pixel line of the next row, moves VC on by 40 as it goes, and at cycle 58 finds RC at 7 again and ends that row too. Do it on every line and, in Bauer’s words, “the RC will always stay at 7 and there will be no c-accesses, but VCBASE is incremented by 40 in every line”. Bauer describes the same state reached from the other side: start a line as a badline, then take the condition away “before cycle 14”.

What appears on a crunched line depends on the mode:

  • Bitmap graphics crunch cleanly. Bitmap addresses come from VC and RC, so each crunched line shows the eighth pixel line of the next row of the picture. The colours come from the character codes, which are not being fetched, so they stay as they were on the last real badline. The Codebase64 page says the colour information is “being corrupt”.
  • Text shows the same characters. In text mode the character codes pick the shapes, and with no new codes each crunched line repeats the bottom line of the last row fetched. For most characters that line is blank.
  • The rows pile up at the top. Bauer found the crunched lines “looking awkward” and suggested hiding them with one of the chip’s invalid graphics modes, which draw black.
  • The screen wraps. Screen memory holds 1,000 characters, but VC counts to 1,023. Crunch enough rows and the chip runs past the end, showing the 24 unused bytes (including the sprite pointers), then wraps to the start of screen memory.

The timing

FLD is forgiving: it only has to keep YSCROLL out of step with the raster, and HCL’s FLD page on the Codebase64 wiki says updating “only every ~6:th rasterline is ok”. Linecrunch has to hit a narrow window on every line it crunches. The linecrunch page says it “requires fairly exact timing since the tweak has to be triggered during a short period of the raster line”, repeated “at the same position each raster line”. In the listing, the write has to land between cycle 58 and the end of the line. A few cycles earlier, in cycles 54 to 57, and the chip carries on with the row instead of finishing it, so the same text line is shown twice, an effect Bauer describes separately as doubled text lines. Earlier still, inside the fetch window, and the badline starts partway along the line, which is the VSP trick.

The routine below waits for raster line $51, just above the badline that starts the fifth character row, then runs a loop that takes exactly 63 cycles, one PAL raster line. Each pass crunches one row. The number of rows rises from 0 to 15 and back, so the screen bounces upwards.

Assembly
; Linecrunch bouncer (ACME syntax) for a PAL C64.
; SYS 49152 to start; RUN/STOP and RESTORE to stop.
        * = $c000
        sei             ; no interrupts: this loop owns the machine
frame   bit $d011       ; wait for raster line 256 or later
        bpl frame       ; (bit 7 of $D011 is bit 8 of the raster)
top     bit $d011       ; then for the start of the next frame
        bmi top
        lda #$1b        ; normal $D011: screen on, 25 rows, YSCROLL 3
        sta $d011
        jsr bounce      ; A = rows to crunch this frame, 0-15
        tax
        beq frame       ; nothing to crunch this frame
        lda #$51        ; wait for raster line $51
wait    cmp $d012
        bne wait
        ldy #10         ; wait most of a line, then check the
delay   dey             ; raster again: this narrows the jitter
        bne delay       ; left by the loop above
        nop
        nop
        cmp $d012
        bne crunch
        bit 0
        nop
crunch  ldy #4          ; one pass takes 63 cycles, one PAL line,
pause   dey             ; so the write to $D011 lands at the
        bne pause       ; same point on every line
        ldy 0
        lda $d012       ; YSCROLL = bottom three bits of this
        and #7          ; raster line
        ora #$18        ; screen on, 25 rows, text mode
        nop
        nop
        nop
        nop
        nop
        nop
        nop
        nop
        nop
        sta $d011       ; lands just after cycle 58
        nop
        dex
        beq frame
        jmp crunch
bounce  lda count       ; count 0-31 as a triangle: 0..15..0,
        inc count       ; one step every other frame
        lsr
        and #$1f
        cmp #$10
        bcc up
        eor #$1f
up      rts
count   !byte 0

Run on a PAL C64 in the VICE emulator, it pulls the BASIC screen up by seven lines for each crunched row, while the top four rows stay put. With five rows crunched, the text that was on the tenth row starts 29 lines higher: 35 lines gained from the crunch, less a six-line band that follows it. That band is the idle state: after the loop stops, YSCROLL is left pointing a few lines ahead, and until the next badline the chip shows the byte at $3FFF. POKE 16383,85 before starting turns the band into stripes; writing a suitable value to $D011 as the loop ends removes it. At fifteen rows the chip runs off the end of screen memory, and the bottom of the display shows a line of coloured junk from the sprite-pointer area followed by the top of the screen again, shifted 24 characters to the right.

First seen on a crack intro

The Codebase64 wiki’s list of demo firsts, a collaborative record begun from a list posted to the CSDb forum, gives the first linecrunch to a crack intro: The Omega Man of Teesside Cracking Service, on the group’s crack of Game Over, dated 22 July 1987. CSDb dates the same crack 19 July 1987. The list’s compilers warn that “some of these issues are subject to dispute”. Commenting on CSDb in December 2006, Graham gave the same answer: “some TCS intro had the first linecrunching”.

The same list names the first demo to put linecrunch together with VSP: Bonanza, coded by Exilon of Microsystems Digital Technologies and placed second in the demo competition at the Alvesta Party in November 1988. Commenting on CSDb in 2006, HCL called it “the first to show a bitmap-scroller using the VSP-trick (+ line-crunching i guess)”. A later CSDb comment by trident explains a second use in the same demo: by crunching on the lines behind a row of sprites, Exilon removed the badlines that would have stolen the processor time needed to open the side borders, and rearranged the bitmap so that the crunched lines still showed a picture.

Linecrunch also turned up in print. In C=Hacking issue 10 (June 1995), Pasi Ojala described watching “demos that used linecrunch routines for whole-screen multicolor-graphics upscrollers” and then used the trick, with FLD to fine-tune the position, to show a 400-line FLI picture: “We have 32 kilobytes of graphics data, this is the main reason we can’t even think about using copying.” His viewer hid the crunched rows at the top by switching on an invalid mode, the same fix Bauer recommends.

How to spot it

The AGSP example on the Codebase64 wiki, which combines VSP and linecrunch, gives a field guide in its comments: “The top five and a bit lines are “wasted” by the line crunch, if you see any demos with the top few lines blanked out and the screen below is moving at a fast rate then you can probably guess it is using a similar method.”

Sources

  • Christian Bauer, The MOS 6567/6569 video controller (VIC-II) and its application in the Commodore 64 (28 August 1996): section 3.5, the badline condition; section 3.7.1, idle and display state and the change “as soon as there is a Bad Line Condition”; section 3.7.2, the rules for VC, VCBASE and RC in cycles 14, 15–54 and 58; section 3.14.4, “Linecrunch”, including the wrap past 1,000 bytes and the invalid-mode blanking; section 3.14.5, doubled text lines; section 3.14.6, the combination with FLD and DMA delay.
  • Codebase64 wiki, “Linecrunch” (web, secondary): the ~9 times the data, the timing, the $3FFF band, and text and bitmap behaviour. https://codebase64.net/doku.php?id=base:linecrunch
  • Codebase64 wiki, “FLD - Flexible Line Distance”, by HCL (web, secondary): FLD’s loose timing. https://codebase64.net/doku.php?id=base:fld
  • Codebase64 wiki, “Any Given Screen Positioning (AGSP)” (web, secondary): the comment on the blanked top lines. https://codebase64.net/doku.php?id=base:agsp_any_given_screen_position
  • Codebase64 wiki, “Demo records and world firsts” (web, secondary): Game Over (22 July 1987) and Bonanza (November 1988), with the compilers’ cautions. https://codebase64.net/doku.php?id=vic:demo:demo_world_records_and_world_firsts
  • CSDb, “Game Over +1D” by Teesside Cracking Service (web, secondary): date and credits. https://csdb.dk/release/?id=38257
  • CSDb, “Bonanza” by Microsystems Digital Technologies (web, secondary): release date, party placing and credits; comments by Graham and HCL (December 2006) and trident (January 2025). https://csdb.dk/release/?id=3391
  • Pasi Ojala, “BFLI - New graphics modes 2”, C=Hacking issue 10, 30 June 1995: linecrunch with delayed badlines to scroll a 400-line FLI picture, and the invalid-mode clipping.

See also

  • FLD: pushing the screen down by holding badlines back
  • VSP: moving the screen sideways
  • AGSP: VSP and linecrunch together
  • FPP: choosing a line of graphics for every raster line
  • VIC-II
  • Stable raster

Checked. Verified against primary sources and signed off by a person.