Below is an example of a tech-tech effect, achieved using a so-called FLI-routine. This is not a tech-tech with a FLI logo, its a simple FLI based routine to allow videoram switching at every rasterline.
The routine differs from this article in that it uses videoram banks to create the tech-tech, not character sets. One could combine these two techniques by cleverly interleaving character sets and videoram to allow for a wider sinus, perhaps even bank switching with $dd00 for even more movement. I'll leave that as an exercise for the reader ;)
I wrote this code as part of an attempt to regain my old VIC-trickery skills, it's been about 25 years since I did a tech-tech. As such, the code may not be up to today's standards. I just published it here since there was no article on tech-tech's using $d011/FLI.
Note: I don't explain FLI in detail in this article, there are other articles here which explain FLI properly.
The code can be assembled by using 64tass:
64tass -C -a -o techtech.prg techtech.asm
I'll use the word 'logo' here to simplify things a bit. My code doesn't actually include a logo, just some text copied from the BASIC boot screen.
Since $d016 only allows us to scroll 8 pixels, we need a way to scroll more. We can achieve that using multiple videoram banks (screens). For pixels 0-7 we use a screen with the logo at its left-most position. For each 8 pixels extra, we put the logo one column to the right from the previous position in a new videoram bank, and so on. An illustration would probably be clearer:
screen: $4800 $4c00 $5000 $5400 and so on.. column: 01234567 01234567 01234567 01234567 logo: HELLO HELLO HELLO HELLO
Now we can select a column-offset of the logo using videoram banks (bit 7-4 of $d018) and we can use $d016 (bit 0-2) to select a pixel offset of 0-7.
For example, we want a line of the logo scrolled 21 pixels from its left-most position. We can achieve this with setting $d016 to 5 (21 & 7 == 5) and setting $d018 to $40 (21 / 8 == 2, multiply with 16 to get the correct videoram bits, and add $20 to skip the first two videoram banks which contain the character set)
lda logo_offset ; some value from the sinus table and #7 ; ORA with $10 for multi-color sta $d016 lda logo_offset asl ; divide by 8, multiply by 16 == multiply by 2 and #$f0 ; mask out lower nybble clc adc #$20 ; add $20 to skip the first two videoram banks ; since the character set occupies those banks sta $d018
This code we need to do for each (raster)line of the logo. But we have a problem…
Manipulating $d016 on each raster line works fine, manipulating $d018 doesn't: the videoram pointer only gets updated on bad lines (every 8th raster line), so we need a way to update the videoram pointer on each raster line. That's were FLI comes in.
Using the so-called FLI technique, we can trigger bad lines at every raster line, allowing us to manipulate the videoram pointer at every raster line.
So our basic tech-tech display routine would look like this: (loop unrolled to keep the FLI bug at three characters while using a single sprite to cover the bug)
; sinus = 0 lda #$1b sta $d011 lda #$00 ; 0 & 7 == 0 sta $d016 lda #$20 ; column 0 sta $d018 ; sinus = 3 ; next rasterline lda #$1c sta $d011 lda #$03 sta $d016 lda #$20 ; still column 0 sta $d018 ; sinus = 6 lda #$1d sta $d011 lda #$06 sta $d016 lda #$20 sta $d018 ; sinus = 9 lda #$1e sta $d011 lda #$01 ; 9 & 7 == 1 sta $d016 lda #$30 sta $d018 ; column 1: 9 pixels means we need to select ; the second videoram bank to move the logo ; one column to the right ; and so on for each line of the logo, wrapping the $d011 ; value around from $1f to $18
Now we can tech-tech the logo by storing the correct values for $d016 and $d018 in the unrolled code, using for example a sinus table. The example code contains a routine which pre-calculates the $d016 and $d018 values from a sinus table for faster performance and another unrolled loop to store these values in the FLI-routine each frame, again for performance.
Here's the actual source code, in 64tass syntax. The FLI routine needs to be unrolled, the sinus calculation can all be done in a loop, but that eats cycles, so I also wrote some unrolled code for the $d016/$d018 values updating.
Before assembling, one should probably comment out the references to the music, I would be surprised to see everyone having their HVSC at “/home/compyx/c64/HSVC” ;)
; vim: set et ts=8 sw=8 sts=8 syntax=64tass : ; ; Simple tech-tech using FLI, 112 pixels wide sinus, using one charset and ; 14 videoram banks in $4000-$7fff. ; ; ; 2016-04-20 ; Music, a nice old school JCH tune music_sid ="/home/compyx/c64/HVSC/MUSICIANS/J/JCH/Ninjackie.sid" music_init = $1000 music_play = $1003 ; height of the tech-tech in pixels TECHTECH_HEIGHT = 88 ; size of a single line of FLI code TECHTECH_MACRO_LEN = 15 ; location of the FLI-bug cover sprite COVER_SPRITE = $7f80 ; zero page zp = $14 ; BASIC SYS line: SYS2061 * = $0801 .word (+), 2016 .null $9e, ^start + .word 0 ; Entry point start jsr $fda3 jsr $fd15 ; jsr $ff5b sei lda #0 sta $d020 sta $d021 jsr tt_setup lda #0 jsr music_init lda #$35 sta $01 lda #$7f sta $dc0d sta $dd0d lda #0 sta $dc0e lda #$01 sta $d01a lda #$1b sta $d011 lda #$2e ldx #<irq1 ldy #>irq1 sta $d012 stx $fffe sty $ffff ldx #<break ldy #>break stx $fffa sty $fffb stx $fffc sty $fffd bit $dc0d bit $dd0d inc $d019 cli jmp * ; IRQ: use the 'double IRQ' trick to stabilize the raster irq1 pha txa pha tya pha lda #$2e ldx #<irq2 ldy #>irq2 sta $d012 stx $fffe sty $ffff lda #1 inc $d019 tsx cli nop nop nop nop nop nop nop nop nop nop nop irq2 txs ldx #8 - dex bne - bit $ea lda $d012 cmp $d012 beq + + ; raster is stable here clc lda #$32 sta $d001 adc #42 sta $d003 lda #0 sta $d027 sta $d028 lda #%00000011 ; stretch cover sprites in X and Y direction sta $d017 sta $d015 sta $d01d lda #$08 ; we need to conver the first four chars on screen, sta $d000 ; since we have the classic three char FLI bug and we sta $d002 ; need one more char to cover since we use $d016, which lda #$7b ; scrolls the FLI bug into the screen sta $d011 lda #2 sta $dd00 ; waste some cycles to start the FLI routine at the correct time ldx #7 - dex bne - nop nop nop ; the unrolled tech-tech FLI routine jsr tt_unrolled ; use invalid graphics mode to cover bugs lda #$7b sta $d011 lda #$03 sta $dd00 lda #$15 sta $d018 lda #8 sta $d016 ldx #89 ; waste cycles until we've covered a full screen row - dex bne - lda #$1b sta $d011 lda #$98 ldx #<irq3 ldy #>irq3 sta $d012 stx $fffe sty $ffff lda #1 sta $d019 pla tay pla tax pla break rti irq3 pha txa pha tya pha dec $d020 jsr tt_sinus_unrolled ; calculate the tech-tech's sinus data dec $d020 jsr music_play lda #0 sta $d020 lda #$2d ldx #<irq1 ldy #>irq1 sta $d012 stx $fffe sty $ffff lda #1 sta $d019 pla tay pla tax pla rti ; Tech-tech setup code tt_setup ; copy CHARGEN ; ; For the 'logo', we use the CBM font lda #$32 sta $01 ldx #0 - lda $d000,x sta $4000,x lda $d100,x sta $4100,x lda $d200,x sta $4200,x lda $d300,x sta $4300,x lda $d400,x sta $4400,x lda $d500,x sta $4500,x lda $d600,x sta $4600,x lda $d700,x sta $4700,x inx bne - ; ldx #7 ; lda #0 ;- sta $47f8,x ; dex ; bpl - ; generate FLI-bug cover sprite ldx #$3f lda #$ff - sta COVER_SPRITE,x dex bpl - ; Set up the videoram banks for the tech-tech effect ; ; The first videoram bank at $4800 contains the 'logo' in its ; default (left-aligned) position, every next videoram bank contains ; the 'logo' shifted one column to the right. This is what makes the ; tech-tech effect possible. ; ; Right now, for the 'logo', we simply copy the BASIC screen data ; from $0400 ldx #0 - lda $0400,x sta $4800,x sta $4c00 + 1,x sta $5000 + 2,x sta $5400 + 3,x sta $5800 + 4,x sta $5c00 + 5,x sta $6000 + 6,x sta $6400 + 7,x sta $6800 + 8,x sta $6c00 + 9,x sta $7000 + 10,x sta $7400 + 11,x sta $7800 + 12,x sta $7c00 + 13,x lda $0500,x sta $4900,x sta $4d00 + 1,x sta $5100 + 2,x sta $5500 + 3,x sta $5900 + 4,x sta $5d00 + 5,x sta $6100 + 6,x sta $6500 + 7,x sta $6900 + 8,x sta $6d00 + 9,x sta $7100 + 10,x sta $7500 + 11,x sta $7900 + 12,x sta $7d00 + 13,x inx bne - ; make last tech-tech line use invalid $d011 mode to mask bug lda #((TECHTECH_HEIGHT - 1) & 7 | $78) sta tt_unrolled + ((TECHTECH_HEIGHT - 1) * TECHTECH_MACRO_LEN) + 1 ; set cover sprite pointers for each videoram bank used lda #$f8 ldx #$4b sta zp stx zp + 1 ldx #0 - lda #(COVER_SPRITE & $3fff) / 64 ldy #0 sta (zp),y iny sta (zp),y lda zp + 1 clc adc #4 sta zp + 1 inx cpx #14 bne - jsr tt_sinus_precalc lda #$37 sta $01 rts ; Precalcute sinus data ; tt_sinus_precalc ldx #0 ldy #0 - lda sinus,y and #7 sta sinus_d016,x sta sinus_d016 + 256,x lda sinus,y ; divide by 8, multiply by 16 to get videoram ; index asl and #$f0 adc #$20 ; C is clear sta sinus_d018,x sta sinus_d018 + 256,x tya clc adc #1 tay inx bne - rts .cerror * > $0fff, "code too long" ; Link music * = $1000 .binary music_sid, $7e ; A single rasterline of FLI-code to display the tech-tech ; ; @param 1: row number (used to calculate the correct $d011 value) ; ; We trigger a badline condition at each rasterline to trigger a videoram ; update which we use to alter the videoram bank ; ttmacro .macro lda #$18 + ((\1 + 3) & 7) sta $d011 lda #$20 ; gets updated in the sinus routine sta $d018 lda #$08 ; gets updated in the sinus routine sta $d016 .endm * = $2000 ; The tech-tech display routine: a simple unrolled FLI routine which also sets ; $d016 at each line tt_unrolled .for row = 0, row < TECHTECH_HEIGHT, row = row + 1 #ttmacro row .next rts .align 256 ; Unrolled sinus updating routine ; ; Uses two tables of 512 bytes each, to allow for X index overflow (we add ; an offset to each sinus_d016 and sinus_d018 table for each row, combine that ; with X register indexing and we would go past the 256-byte mark in a table) tt_sinus_unrolled _index ldx #0 .for row = 0, row < TECHTECH_HEIGHT, row = row + 1 lda sinus_d018 + row,x sta tt_unrolled + (row * TECHTECH_MACRO_LEN) + 6 lda sinus_d016 + row,x sta tt_unrolled + (row * TECHTECH_MACRO_LEN) + 11 .next lda _index + 1 clc adc #2 sta _index + 1 rts ; Sinus used for the tech-tech effect: 112 pixels wide since we use 14 ; videoram banks .align 256 sinus .byte 55.5 + 56 * cos(range(256) * rad(360.0/256)) ; Precalculated $d016 values sinus_d016 .fill 512, 0 ; Precalculated $d018 values sinus_d018 .fill 512,0