diff --git a/README.md b/README.md index 6d667a7..cd47f86 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,12 @@ # C64 Demo — `main.asm` -A minimal Commodore 64 demo written in 6502 assembly (compiled with the ACME assembler). The program prints two lines of text on the screen, then enters an infinite loop that cycles the border color. +A Commodore 64 intro written in 6502 assembly (compiled with the ACME assembler). The screen shows: + +- giant **TTG** letters in the top half (built from 8×8 bitmaps, drawn with solid block characters), +- **TELETYPE GAMES** centered below, +- a horizontally scrolling credits line (`MR.ZERO - TASNADI ZSOLT / MR.ONE - TARI BALAZS / MR.TWO - TIMAR ZOLTAN / MR.THREE - MEZO BELA`), +- **WWW.TELETYPEGAMES.ORG**, +- **SZEGED, 2026**. ## Build and run @@ -22,80 +28,38 @@ RUN ### 1. BASIC stub (from `$0801`) -The C64 looks for programs at the start of the BASIC memory area (`$0801`). We place a one-line BASIC program here: +A hand-encoded one-line BASIC program (`10 SYS 2064`) so the user only has to type `RUN`; it jumps to the machine-code entry point at `$0810`. -``` -10 SYS 2064 -``` +### 2. Initialization -This lets the user just type `RUN` — the BASIC interpreter then transfers control to the machine-code entry point at address `2064` ($0810). The `!byte` directives hand-encode the BASIC line: link pointer, line number, `SYS` token (`$9e`), the `" 2064"` ASCII digits, end-of-line zero, and end-of-program marker. +Border and background are set to black, then screen RAM (`$0400`) is filled with spaces and color RAM (`$D800`) with white. Everything is drawn by writing to screen RAM directly — the KERNAL `CHROUT` routine is not used. -### 2. Entry point (`$0810` = 2064) +### 3. Giant letters -```asm -* = $0810 -``` +Each big letter is an 8×8 bitmap (`bigfont`, one byte per row, MSB = left pixel). The drawing loop walks the three letters, shifts each bitmap row out bit by bit, and writes an inverse-space block (`$A0`) plus yellow into color RAM for every set bit. Letters are 8 columns wide with a 2-column gap, so the 28-column logo is centered starting at column 6, row 2. -Machine code starts here. +### 4. Static texts -### 3. KERNAL and VIC-II addresses +Null-loop copies place the screen-code strings (`!scr`) at fixed, centered screen offsets: -| Symbol | Address| Meaning | -|-----------|--------|----------------------------------| -| `CHROUT` | `$FFD2`| KERNAL character output routine | -| `BORDER` | `$D020`| VIC-II border color register | -| `BG` | `$D021`| VIC-II background color register | +| Text | Row | Color | +|-------------------------|-----|-------------| +| `TELETYPE GAMES` | 12 | white | +| scroller line | 14 | cyan | +| `WWW.TELETYPEGAMES.ORG` | 17 | light green | +| `SZEGED, 2026` | 19 | light grey | -### 4. `start` — initialization +### 5. Scroller (main loop) -- **Background:** light blue (`14`) into `$D021`. -- **Border:** dark blue (`6`) into `$D020`. -- **Clear screen:** PETSCII control code `$93` sent to `CHROUT`. -- **Text color:** white (`$05`). - -### 5. `print_loop` — printing the text - -Classic null-terminated string output: - -```asm -ldx #0 -print_loop: - lda message,x ; load character using X as index - beq color_loop ; if 0 → done - jsr CHROUT ; otherwise print it - inx - jmp print_loop -``` - -At the `message` label there is PETSCII text separated by line breaks (`13`), terminated with a `0` byte. - -### 6. `color_loop` — infinite border flashing - -```asm -color_loop: - inc BORDER ; border color +1 - ; two nested delay loops (256*256 iterations) - jmp color_loop -``` - -`inc $D020` increments the lower 4 bits of the border color on every iteration (the VIC-II only uses those anyway), cycling through all 16 colors. The nested `dex`/`dey` loop is pure busy-wait so the eye can perceive the change — the C64 runs at ~1 MHz, and without the delay the flashing would be invisibly fast. - -### 7. `message` — data - -```asm -message: - !text "HELLO, COMMODORE 64!" - !byte 13, 13 ; two line breaks - !text "TELETYPE GAMES DEMO" - !byte 13, 0 ; line break + end-of-string -``` +The main loop synchronizes to the display by waiting for raster line `$FE` (`$D012`), giving one iteration per frame. Every `SCROLL_SPEED` (4) frames it shifts row 14 one character to the left and feeds the next character of `scrolltext` into the rightmost column; the index wraps at the end of the text, so the credits loop forever. ## Memory map ``` $0801 .. $080F BASIC stub (10 SYS 2064) -$0810 .. ... machine code (start, print_loop, color_loop, message) -$D020 border color (visible while being written) -$D021 background color -$FFD2 KERNAL CHROUT +$0810 .. ... machine code + font bitmaps + texts + variables +$0400 .. $07E7 screen RAM (written directly) +$D800 .. color RAM +$D012 raster line (frame sync) +$D020 / $D021 border / background color ``` diff --git a/main.asm b/main.asm index 7299143..b4e97de 100644 --- a/main.asm +++ b/main.asm @@ -1,5 +1,5 @@ ; ----------------------------------------------------------- -; main.asm - C64 demo: text output + border flashing +; main.asm - C64 demo: giant TTG logo + credits scroller ; Build: acme -f cbm -o main.prg main.asm ; ----------------------------------------------------------- @@ -18,45 +18,238 @@ * = $0810 ; = 2064 in decimal -; ---- KERNAL routines ---- -CHROUT = $ffd2 ; character output +; ---- hardware registers ---- BORDER = $d020 ; border color register BG = $d021 ; background color register +RASTER = $d012 ; current raster line +SCREEN = $0400 ; screen RAM (40x25 chars) +COLRAM = $d800 ; color RAM + +; ---- colors ---- +BLACK = 0 +WHITE = 1 +CYAN = 3 +YELLOW = 7 +LGREEN = 13 +LGREY = 15 + +; ---- screen layout ---- +BIGROW = 2 ; top row of the giant letters +BIGCOL = 6 ; left column (3*8 + 2*2 = 28 wide, centered) +TITLE_POS = 12*40 + 13 ; "TELETYPE GAMES" (14 chars, centered) +SCROLLROW = 14 ; scroller row +WWW_POS = 17*40 + 9 ; "WWW.TELETYPEGAMES.ORG" (21 chars) +CITY_POS = 19*40 + 14 ; "SZEGED, 2026" (12 chars) +SCROLL_SPEED = 4 ; frames per scroll step + +; ---- zero page pointers ---- +scrlo = $fb +scrhi = $fc +collo = $fd +colhi = $fe start: - lda #14 ; light blue background - sta BG - lda #6 ; dark blue border + lda #BLACK sta BORDER + sta BG - lda #$93 ; CLR/HOME - clear screen - jsr CHROUT - lda #$05 ; white text color - jsr CHROUT - + ; clear screen RAM, fill color RAM with white ldx #0 -print_loop: - lda message,x - beq color_loop ; if 0 -> text done - jsr CHROUT +clear_loop: + lda #$20 ; space + sta SCREEN,x + sta SCREEN+$100,x + sta SCREEN+$200,x + sta SCREEN+$2e8,x + lda #WHITE + sta COLRAM,x + sta COLRAM+$100,x + sta COLRAM+$200,x + sta COLRAM+$2e8,x inx - jmp print_loop + bne clear_loop -; infinite color flasher -color_loop: - inc BORDER ; increment border color +; ---- draw the giant TTG letters from 8x8 bitmaps ---- + lda #0 + sta letter +letter_loop: + ldx letter + lda letter_off,x + clc + adc #<(SCREEN + BIGROW*40 + BIGCOL) + sta scrlo + lda #>(SCREEN + BIGROW*40 + BIGCOL) + adc #0 + sta scrhi + lda letter_off,x + clc + adc #<(COLRAM + BIGROW*40 + BIGCOL) + sta collo + lda #>(COLRAM + BIGROW*40 + BIGCOL) + adc #0 + sta colhi + + lda letter ; bitmap index = letter * 8 + asl + asl + asl + tax + lda #8 + sta rowcount +row_loop: + lda bigfont,x + sta curbits ldy #0 -delay_outer: - ldx #0 -delay_inner: - dex - bne delay_inner - dey - bne delay_outer - jmp color_loop +bit_loop: + asl curbits ; leftmost pixel first + bcc no_plot + lda #$a0 ; inverse space = solid block + sta (scrlo),y + lda #YELLOW + sta (collo),y +no_plot: + iny + cpy #8 + bne bit_loop + lda scrlo ; advance both pointers one screen row + clc + adc #40 + sta scrlo + bcc scr_nocarry + inc scrhi +scr_nocarry: + lda collo + clc + adc #40 + sta collo + bcc col_nocarry + inc colhi +col_nocarry: + inx + dec rowcount + bne row_loop -message: - !text "HELLO, COMMODORE 64!" - !byte 13, 13 - !text "TELETYPE GAMES DEMO" - !byte 13, 0 + inc letter + lda letter + cmp #3 + bne letter_loop + +; ---- static texts ---- + ldx #0 +title_loop: + lda txt_title,x + sta SCREEN+TITLE_POS,x + lda #WHITE + sta COLRAM+TITLE_POS,x + inx + cpx #txt_title_end - txt_title + bne title_loop + + ldx #0 +www_loop: + lda txt_www,x + sta SCREEN+WWW_POS,x + lda #LGREEN + sta COLRAM+WWW_POS,x + inx + cpx #txt_www_end - txt_www + bne www_loop + + ldx #0 +city_loop: + lda txt_city,x + sta SCREEN+CITY_POS,x + lda #LGREY + sta COLRAM+CITY_POS,x + inx + cpx #txt_city_end - txt_city + bne city_loop + + ; scroller row color + ldx #39 + lda #CYAN +scrollcol_loop: + sta COLRAM+SCROLLROW*40,x + dex + bpl scrollcol_loop + + lda #SCROLL_SPEED + sta framecnt + lda #0 + sta scrollidx + +; ---- main loop: scroll the credits line ---- +main: +wait_raster: ; wait for raster line $fe (bottom of screen) + lda RASTER + cmp #$fe + bne wait_raster +wait_raster2: ; ...and wait until it passes -> exactly one frame + lda RASTER + cmp #$fe + beq wait_raster2 + + dec framecnt + bne main + lda #SCROLL_SPEED + sta framecnt + + ; shift scroller row one char to the left + ldx #0 +shift_loop: + lda SCREEN+SCROLLROW*40+1,x + sta SCREEN+SCROLLROW*40,x + inx + cpx #39 + bne shift_loop + + ; fetch next char of the scrolltext into the rightmost column + ldx scrollidx + lda scrolltext,x + sta SCREEN+SCROLLROW*40+39 + inx + cpx #scrolltext_end - scrolltext + bne no_wrap + ldx #0 +no_wrap: + stx scrollidx + jmp main + +; ---- data ---- + +; 8x8 bitmaps of the giant letters (one byte per row, MSB = left pixel) +bigfont: + ; T + !byte $ff, $ff, $3c, $3c, $3c, $3c, $3c, $3c + ; T + !byte $ff, $ff, $3c, $3c, $3c, $3c, $3c, $3c + ; G + !byte $7e, $c3, $c0, $c0, $cf, $c3, $c3, $7e + +letter_off: ; column offset of each letter (8 wide + 2 gap) + !byte 0, 10, 20 + +; texts in screen codes (!scr); lowercase source shows as uppercase on C64 +txt_title: + !scr "teletype games" +txt_title_end: + +txt_www: + !scr "www.teletypegames.org" +txt_www_end: + +txt_city: + !scr "szeged, 2026" +txt_city_end: + +scrolltext: + !scr "mr.zero - tasnadi zsolt / mr.one - tari balazs / " + !scr "mr.two - timar zoltan / mr.three - mezo bela / " +scrolltext_end: + +; ---- variables ---- +letter: !byte 0 +rowcount: !byte 0 +curbits: !byte 0 +framecnt: !byte 0 +scrollidx: !byte 0