; Dots tunnel, version 6: the v5 on a screen of 256 x 256 pixels, where a
; line takes 64 bytes and the column joins the line address with an or.
; RASM computes the tables while it assembles.
; rasm tunnel-v6.asm -o tunnel-v6 writes tunnel-v6.sna.

        buildsna
        bankset 0

YLO_C0 = #4000                  ; line y -> address in the #C000 screen
YHI_C0 = #4100
CENTER = #4200
SHAPE = #4A00
YLO_80 = #6A00                  ; and in the #8000 screen
YHI_80 = #6B00
XMASK = #7A00                   ; built by make_x_tables
COLX = #7B00
LIST_C0 = #7C00                 ; the lit bytes in the #C000 screen
LIST_80 = #7E00                 ; and in the #8000 screen
STACK = #1000                   ; the real stack, below the code
SCREEN_C0 = #C0                 ; high bytes of the two screens
SCREEN_80 = #80
RINGS = 16
DOTS = 16
CX = 128                        ; the parameters of tables.asm, centred on the
CY = 128                        ; 256 x 256 screen
P = 90
R = 1
Z0 = 1
DZ = 0.15
AX = 72
AY = 50

; Gate Array colours (#40 + hardware colour number)
BLACK = #54
BLUE = #44
SKY_BLUE = #57
BRIGHT_WHITE = #4B
RED = #4C

        macro border,colour
        ld bc,#7F10             ; Gate Array: select the border
        out (c),c
        ld a,{colour}
        out (c),a
        mend

; RASM's sin() and cos() take degrees: a full turn is 360, not 2*PI.
; floor(x+0.5) rounds like INT(x+0.5) in BASIC.
; A line takes 64 bytes: line y starts at 64*(y>>3) + 2048*(y and 7). The low
; byte is a multiple of 64, and a byte column (0 to 63) fills its 6 low bits.
        org YLO_C0
        repeat 256,y,0
        db ((y>>3)*64)&255
        rend
        repeat 256,y,0          ; YHI_C0 = YLO_C0 + 256
        db SCREEN_C0+(((y>>3)*64+(y&7)*2048)>>8)
        rend

        org CENTER
        repeat 16,q,0
        repeat 64,phase,0
        db CX+floor(AX*q/15*sin(360*phase/64)+0.5)
        db CY+floor(AY*q/15*sin(720*phase/64+0.7*180/pi)+0.5)
        rend
        rend

        org SHAPE
        repeat 64,d,0
s = P*R/(Z0+d*DZ)
        repeat 4,rot,0
        repeat DOTS,k,0
angle = 360*(4*k+rot)/64
        db floor(s*cos(angle)+0.5)&255
        db floor(s*sin(angle)+0.5)&255
        rend
        rend
        rend

        org YLO_80
        repeat 256,y,0
        db ((y>>3)*64)&255
        rend
        repeat 256,y,0          ; YHI_80 = YLO_80 + 256
        db SCREEN_80+(((y>>3)*64+(y&7)*2048)>>8)
        rend

        org #1000
        run start

start:  di
        ld sp,STACK
        call init
        call make_x_tables
        ld hl,LIST_C0
        ld a,SCREEN_C0
        call fill_list
        ld hl,LIST_80
        ld a,SCREEN_80
        call fill_list

main:   ld sp,STACK             ; the drawing left SP in a list
        call wait_vbl
        ld a,(work)
        call show_screen        ; show the screen we have just drawn
        ld a,(work)
        xor SCREEN_C0 ^ SCREEN_80
        ld (work),a             ; and draw into the other one
        ld a,(ypage)
        xor (YLO_C0>>8)^(YLO_80>>8)
        ld (ypage),a            ; the YLO table of the work screen
        repeat DOTS,n,0
        ld (ypatch{n}+1),a      ; goes into the ld h of each dot
        rend
        ld a,(erase_list)
        xor (LIST_C0>>8)^(LIST_80>>8)  ; the list follows the screen
        ld (erase_list),a

        border RED              ; work starts

        ld a,(erase_list)       ; clear the bytes lit two pictures earlier
        ld h,a
        ld l,0
        ld sp,hl                ; SP walks through the list
        ld c,0
        repeat 256              ; one pop per address, without a counter
        pop hl
        ld (hl),c
        rend
        ; SP now points to the end of the list: the push of each dot fills it
        ; again, downwards. Up to `jp main`, no call and no other push.

        ld hl,t
        inc (hl)
        ld a,(hl)
        neg
        and 63
        ld (depth),a            ; ring 0: d = -t and 63

        ld iyl,RINGS            ; IYL: rings left
ring_loop:
        ld a,(depth)            ; IXL, IXH: the centre of ring d
        ld c,a
        and #3C                 ; (d >> 2) * 4
        ld l,a
        ld h,0
        add hl,hl
        add hl,hl
        add hl,hl
        add hl,hl
        add hl,hl               ; (d >> 2) * 128
        ld a,(t)
        add a,c
        and #FC
        rrca                    ; phase * 2, phase = ((t + d) >> 2) and 63
        or l
        ld l,a
        ld a,h
        add a,CENTER>>8
        ld h,a
        ld a,(hl)
        ld ixl,a
        inc hl
        ld a,(hl)
        ld ixh,a

        ld a,c                  ; B = PENS(d >> 4)
        rlca
        rlca
        rlca
        rlca
        and 3
        ld e,a
        ld d,0
        ld hl,PENS
        add hl,de
        ld b,(hl)

        ld a,(t)                ; DE = SHAPE + d * 128 + rot * 32
        and #0C
        rlca
        rlca
        rlca                    ; rot * 32, rot = (t >> 2) and 3
        ld e,a
        ld a,c
        srl a                   ; A = d >> 1, carry = d and 1
        jr nc,even_depth
        set 7,e                 ; + 128 if d is odd
even_depth:
        add a,SHAPE>>8
        ld d,a

        repeat DOTS,n,0         ; the 16 dots, one after the other
        ld a,(de)               ; dx
        inc e                   ; the 32 bytes of a ring stay in one page
        add a,ixl               ; x = ring centre x + dx
        ld l,a
        ld h,XMASK>>8
        ld a,(hl)               ; bits of the pixel in its byte
        and b                   ; in the ink
        ex af,af'               ; kept aside for the end
        inc h                   ; COLX = XMASK + 256
        ld c,(hl)               ; byte column: x >> 2
        ld a,(de)               ; dy
        inc e
        add a,ixh               ; y = ring centre y + dy
        ld l,a
ypatch{n}:
        ld h,0                  ; YLO page of the work screen, set by main
        ld a,(hl)
        or c                    ; low byte of the line + column: no carry
        inc h                   ; YHI = YLO + 256
        ld h,(hl)               ; high byte, screen start included
        ld l,a
        ex af,af'               ; the bits of the pixel again
        or (hl)                 ; keep the other pixels of the byte
        ld (hl),a
        push hl                 ; record the address for the clearing
        rend

        ld a,(depth)            ; next ring: d + 4
        add a,4
        and 63
        ld (depth),a
        dec iyl
        jp nz,ring_loop

        border BLACK            ; work ends
        jp main

; init: mode 1, ROMs off, palette, screen of 256 x 256.
init:   ld bc,#7F8D
        out (c),c
        ld hl,palette
        ld e,0
next_ink:
        out (c),e               ; E < #10: select ink E
        ld a,(hl)
        out (c),a               ; #40 to #5F: set its colour
        inc hl
        inc e
        ld a,e
        cp 4
        jr nz,next_ink
        ld a,#10                ; select the border
        out (c),a
        ld a,BLACK
        out (c),a
        ld hl,crtc_256
        ld e,4
next_reg:
        ld b,#BC                ; CRTC: select the register
        ld a,(hl)
        out (c),a
        inc hl
        inc b                   ; #BD: write into it
        ld a,(hl)
        out (c),a
        inc hl
        dec e
        jr nz,next_reg
        ret

; make_x_tables: for each x from 0 to 255, XMASK(x) = the bits of pixel
; x and 3 in its byte (#88 shifted right x and 3 times), COLX(x) = x >> 2.
make_x_tables:
        ld hl,XMASK
next_x: ld a,l
        and 3
        ld b,a
        ld a,#88                ; bits of the left pixel
        jr z,mask_done
shift_mask:
        srl a
        djnz shift_mask
mask_done:
        ld (hl),a
        inc h                   ; COLX
        ld a,l
        srl a
        srl a
        ld (hl),a
        dec h
        inc l
        jr nz,next_x
        ret

; fill_list: fill list HL with 256 copies of the address A * 256, the first
; byte of a screen that is still black: the first clearing changes nothing.
fill_list:
        ld b,0
next_entry:
        ld (hl),0
        inc hl
        ld (hl),a
        inc hl
        djnz next_entry
        ret

; show_screen: the CRTC will show the screen whose high byte is A (#C0 or #80),
; from the next picture on.
show_screen:
        ld bc,#BC0C             ; CRTC: select register 12
        out (c),c
        rrca
        rrca
        and #30                 ; #C0 -> #30, #80 -> #20
        inc b                   ; #BD: write to the register
        out (c),a
        ret

; wait_vbl: wait for the start of the frame flyback.
wait_vbl:
        ld b,#F5                ; PPI port B, bit 0 = VBL
vbl_end:
        in a,(c)
        rra
        jr c,vbl_end            ; already in the VBL: wait for its end
vbl_start:
        in a,(c)
        rra
        jr nc,vbl_start
        ret

palette:
        db BLACK,BLUE,SKY_BLUE,BRIGHT_WHITE
; R1 = 32 words (64 bytes) per line, R6 = 32 rows of 8 lines; R2 and R7
; move the syncs to keep the picture in the middle of the monitor.
crtc_256:
        db 1,32, 2,42, 6,32, 7,34
PENS:   db #FF,#0F,#F0,#F0      ; 4 pixels of ink 3, 2, 1, 1 for d >> 4

work:   db SCREEN_C0            ; high byte of the work screen
erase_list: db LIST_C0>>8       ; high byte of its list
t:      db 0
depth:  db 0
ypage:  db YLO_C0>>8            ; switched to YLO_80 by the first picture
