; Dots tunnel, version 7 : la v6 avec 25 points par anneau, 400 en tout.
; RASM calcule les tables pendant l'assemblage.
; rasm tunnel-v7-fr.asm -o tunnel-v7 écrit tunnel-v7.sna.

        buildsna
        bankset 0

YLO_C0 = #2000                  ; ligne y -> adresse dans l'écran #C000
YHI_C0 = #2100
YLO_80 = #2200                  ; et dans l'écran #8000
YHI_80 = #2300
CENTER = #2400
XMASK = #2C00                   ; construite par make_x_tables
COLX = #2D00
LIST_C0 = #3000                 ; les octets allumés dans l'écran #C000
LIST_80 = #3400                 ; et dans l'écran #8000
SHAPE = #4000                   ; 16 Ko, jusqu'à #7FFF
STACK = #1000                   ; la vraie pile, sous le code
SCREEN_C0 = #C0                 ; poids forts des deux écrans
SCREEN_80 = #80
RINGS = 16
DOTS = 25
ROW = 64                        ; une rangée de SHAPE : 2 * DOTS octets, complétée
CX = 128                        ; les paramètres de tables-fr.asm, centrés sur
CY = 128                        ; l'écran de 256 x 256
P = 90
R = 1
Z0 = 1
DZ = 0.15
AX = 72
AY = 50

; Couleurs du Gate Array (#40 + numéro de la couleur matérielle)
BLACK = #54
BLUE = #44
SKY_BLUE = #57
BRIGHT_WHITE = #4B
RED = #4C

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

; sin() et cos() de RASM prennent des degrés : un tour vaut 360, pas 2*PI.
; floor(x+0.5) arrondit comme le INT(x+0.5) du BASIC.
; Une ligne fait 64 octets : la ligne y commence en 64*(y>>3) + 2048*(y and 7).
; Son octet bas est un multiple de 64, et une colonne d'octet (0 à 63)
; remplit ses 6 bits bas.
        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 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 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)/(4*DOTS)
        db floor(s*cos(angle)+0.5)&255
        db floor(s*sin(angle)+0.5)&255
        rend
        defs ROW-2*DOTS,0       ; une rangée reste dans sa page : inc e suffit
        rend
        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             ; le tracé a laissé SP dans une liste
        call wait_vbl
        ld a,(work)
        call show_screen        ; affiche l'écran qu'on vient de dessiner
        ld a,(work)
        xor SCREEN_C0 ^ SCREEN_80
        ld (work),a             ; et dessine dans l'autre
        ld a,(ypage)
        xor (YLO_C0>>8)^(YLO_80>>8)
        ld (ypage),a            ; la table YLO de l'écran de travail
        repeat DOTS,n,0
        ld (ypatch{n}+1),a      ; va dans le ld h de chaque point
        rend
        ld a,(erase_list)
        xor (LIST_C0>>8)^(LIST_80>>8)  ; la liste suit l'écran
        ld (erase_list),a

        border RED              ; début du travail

        ld a,(erase_list)       ; efface les octets allumés deux images plus tôt
        ld h,a
        ld l,0
        ld sp,hl                ; SP parcourt la liste
        ld c,0
        repeat RINGS*DOTS       ; un pop par adresse, sans compteur
        pop hl
        ld (hl),c
        rend
        ; SP pointe maintenant sur la fin de la liste : le push de chaque point la
        ; remplit de nouveau, en descendant. Jusqu'à `jp main`, ni call ni autre push.

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

        ld iyl,RINGS            ; IYL : anneaux restants
ring_loop:
        ld a,(depth)            ; IXL, IXH : le centre de l'anneau 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 * 256 + rot * 64
        and #0C
        rlca
        rlca
        rlca
        rlca                    ; rot * 64, rot = (t >> 2) and 3
        ld e,a
        ld a,c
        add a,SHAPE>>8
        ld d,a

        repeat DOTS,n,0         ; les points de l'anneau, l'un après l'autre
        ld a,(de)               ; dx
        inc e                   ; une rangée de SHAPE reste dans sa page
        add a,ixl               ; x = centre x de l'anneau + dx
        ld l,a
        ld h,XMASK>>8
        ld a,(hl)               ; bits du pixel dans son octet
        and b                   ; dans l'encre
        ex af,af'               ; mis de côté pour la fin
        inc h                   ; COLX = XMASK + 256
        ld c,(hl)               ; colonne d'octet : x >> 2
        ld a,(de)               ; dy
        inc e
        add a,ixh               ; y = centre y de l'anneau + dy
        ld l,a
ypatch{n}:
        ld h,0                  ; page YLO de l'écran de travail, écrite par main
        ld a,(hl)
        or c                    ; poids faible de la ligne + colonne : sans retenue
        inc h                   ; YHI = YLO + 256
        ld h,(hl)               ; poids fort, début de l'écran compris
        ld l,a
        ex af,af'               ; de nouveau les bits du pixel
        or (hl)                 ; garde les autres pixels de l'octet
        ld (hl),a
        push hl                 ; note l'adresse pour l'effacement
        rend

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

        border BLACK            ; fin du travail
        jp main

; init : mode 1, ROM coupées, palette, écran de 256 x 256.
init:   ld bc,#7F8D
        out (c),c
        ld hl,palette
        ld e,0
next_ink:
        out (c),e               ; E < #10 : choisit l'encre E
        ld a,(hl)
        out (c),a               ; #40 à #5F : lui donne sa couleur
        inc hl
        inc e
        ld a,e
        cp 4
        jr nz,next_ink
        ld a,#10                ; choisit la bordure
        out (c),a
        ld a,BLACK
        out (c),a
        ld hl,crtc_256
        ld e,4
next_reg:
        ld b,#BC                ; CRTC : choisit le registre
        ld a,(hl)
        out (c),a
        inc hl
        inc b                   ; #BD : écrit dans le registre
        ld a,(hl)
        out (c),a
        inc hl
        dec e
        jr nz,next_reg
        ret

; make_x_tables : pour chaque x de 0 à 255, XMASK(x) = les bits du pixel
; x and 3 dans son octet (#88 décalé x and 3 fois à droite), COLX(x) = x >> 2.
make_x_tables:
        ld hl,XMASK
next_x: ld a,l
        and 3
        ld b,a
        ld a,#88                ; bits du pixel de gauche
        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 : remplit la liste HL de RINGS * DOTS fois l'adresse A * 256,
; le premier octet d'un écran encore noir : le premier effacement n'y change rien.
fill_list:
        ld c,RINGS
next_ring:
        ld b,DOTS
next_entry:
        ld (hl),0
        inc hl
        ld (hl),a
        inc hl
        djnz next_entry
        dec c
        jr nz,next_ring
        ret

; show_screen : le CRTC affichera l'écran de poids fort A (#C0 ou #80)
; à partir de la prochaine image.
show_screen:
        ld bc,#BC0C             ; CRTC : choisit le registre 12
        out (c),c
        rrca
        rrca
        and #30                 ; #C0 -> #30, #80 -> #20
        inc b                   ; #BD : écrit dans le registre
        out (c),a
        ret

; wait_vbl : attend le début du retour de trame.
wait_vbl:
        ld b,#F5                ; PPI port B, bit 0 = VBL
vbl_end:
        in a,(c)
        rra
        jr c,vbl_end            ; déjà dans la VBL : attend sa fin
vbl_start:
        in a,(c)
        rra
        jr nc,vbl_start
        ret

palette:
        db BLACK,BLUE,SKY_BLUE,BRIGHT_WHITE
; R1 = 32 mots (64 octets) par ligne, R6 = 32 rangées de 8 lignes ; R2 et R7
; déplacent les synchros pour garder l'image au milieu du moniteur.
crtc_256:
        db 1,32, 2,42, 6,32, 7,34
PENS:   db #FF,#0F,#F0,#F0      ; 4 pixels d'encre 3, 2, 1, 1 selon d >> 4

work:   db SCREEN_C0            ; poids fort de l'écran de travail
erase_list: db LIST_C0>>8       ; poids fort de sa liste
t:      db 0
depth:  db 0
ypage:  db YLO_C0>>8            ; passe à YLO_80 dès la première image
