From 68d64b4e122a8809d509cb17c88a1977afac9958 Mon Sep 17 00:00:00 2001 From: Zsolt Tasnadi Date: Mon, 7 Sep 2026 23:20:17 +0200 Subject: [PATCH] Teletype Games Plus/4 demo: a TED colour-wash title card in 6502 assembly, on the served plus4 pipeline --- .gitignore | 4 + .vscode/tasks.json | 45 +++++++++++ .woodpecker.yaml | 2 + Makefile | 47 ++++++++++++ README.md | 59 +++++++++++++++ main.asm | 184 +++++++++++++++++++++++++++++++++++++++++++++ metadata.json | 10 +++ 7 files changed, 351 insertions(+) create mode 100644 .gitignore create mode 100644 .vscode/tasks.json create mode 100644 .woodpecker.yaml create mode 100644 Makefile create mode 100644 README.md create mode 100644 main.asm create mode 100644 metadata.json diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..cacfcf7 --- /dev/null +++ b/.gitignore @@ -0,0 +1,4 @@ +.claude +*.prg +.version +*-*.metadata.json diff --git a/.vscode/tasks.json b/.vscode/tasks.json new file mode 100644 index 0000000..9de1d66 --- /dev/null +++ b/.vscode/tasks.json @@ -0,0 +1,45 @@ +{ + "version": "2.0.0", + "tasks": [ + { + "label": "deps", + "type": "shell", + "command": "make deps", + "problemMatcher": [] + }, + { + "label": "build", + "type": "shell", + "command": "make build", + "group": { + "kind": "build", + "isDefault": true + }, + "problemMatcher": [] + }, + { + "label": "run", + "type": "shell", + "command": "make run", + "problemMatcher": [] + }, + { + "label": "clear", + "type": "shell", + "command": "make clear", + "problemMatcher": [] + }, + { + "label": "rebuild", + "type": "shell", + "command": "make rebuild", + "problemMatcher": [] + }, + { + "label": "rebuildrun", + "type": "shell", + "command": "make rebuildrun", + "problemMatcher": [] + } + ] +} diff --git a/.woodpecker.yaml b/.woodpecker.yaml new file mode 100644 index 0000000..1ec70bf --- /dev/null +++ b/.woodpecker.yaml @@ -0,0 +1,2 @@ +# The CI pipeline is served by the update server: GET /build/config?platform=plus4 +platform: plus4 diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..307d7fc --- /dev/null +++ b/Makefile @@ -0,0 +1,47 @@ +# ----------------------------------------- +# Makefile – Plus/4 project builder (ACME + VICE) +# ----------------------------------------- + +PROJECT = plus4-demo + +ASM = acme +EMU = xplus4 +SRC = main.asm +SRCS = $(wildcard *.asm) +TARGET ?= $(PROJECT).prg + +METADATA = metadata.json + +UNAME_S := $(shell uname -s) + +# CI/CD variables +VERSION_FILE = .version + +all: build + +deps: +ifeq ($(UNAME_S),Darwin) + @command -v brew >/dev/null 2>&1 || { echo "Homebrew kell ehhez. Telepitsd: https://brew.sh"; exit 1; } + brew install acme vice +else + @echo "A 'deps' target csak macOS-en (Darwin) automatikus. Detektalt OS: $(UNAME_S)" + @echo "Telepitsd kezzel: acme assembler + VICE emulator (xplus4)." + @exit 1 +endif + +build: $(TARGET) + +$(TARGET): $(SRCS) + $(ASM) -f cbm -o $(TARGET) $(SRC) + +run: build + $(EMU) $(TARGET) + +clear: + rm -f $(TARGET) $(PROJECT)-*.prg $(PROJECT)-*.metadata.json $(VERSION_FILE) + +rebuild: clear build + +rebuildrun: clear build run + +.PHONY: all deps build run clear rebuild rebuildrun diff --git a/README.md b/README.md new file mode 100644 index 0000000..e24d3b2 --- /dev/null +++ b/README.md @@ -0,0 +1,59 @@ +# Plus/4 Demo — Teletype Games title card + +A Commodore Plus/4 program written in 6502 assembly (built with the ACME assembler): the smallest complete example of a Plus/4 title that goes through the Teletype Games build pipeline. It clears the screen, paints the Teletype Games title card, runs a gold colour wash across the title in sync with the raster beam, blinks a prompt, and hands control back to BASIC on any key. + +The Plus/4 sibling of the [C64 demo](https://git.teletypegames.org/games/c64-demo): same assembler, same Makefile, same pipeline shape — only the machine differs. + +## Controls + +| Key | Action | +|-----|--------| +| any key | return to BASIC (colours restored, screen cleared) | + +Keys are read straight from the keyboard latch (`$FD30` / `$FF08`), the KERNAL IRQ is switched off while the demo runs. + +## Build and run + +```sh +make build # produce plus4-demo.prg +make run # build + launch in VICE (xplus4) +make rebuildrun # clean rebuild + run +make deps # on macOS: install acme + vice via Homebrew +``` + +Loading the `.prg` on a real or emulated Plus/4: + +``` +LOAD"*",8,1 +RUN +``` + +Headless smoke test with VICE (runs for a few emulated seconds, then writes a screenshot): + +```sh +xplus4 +sound -warp -limitcycles 30000000 -exitscreenshot shot.png -autostartprgmode 1 plus4-demo.prg +``` + +## How it works + +- **BASIC stub** at `$1001`: `10 SYS 4109`, so `RUN` starts the code at `$100D`. +- **Screen** is written directly: screen RAM at `$0C00`, colour RAM at `$0800`. A Plus/4 colour byte is `luminance << 4 | hue`, which is what the wash exploits — the same gold hue at eight luminances, peaking in white. +- **Timing**: no interrupts; the main loop polls the TED raster register (`$FF1D`) and does one pass per frame. +- **Text**: the `+prtext` macro copies a fixed screen-code string to a row/column with one colour. Strings are written in lowercase ASCII, which ACME's `!scr` maps to the uppercase screen codes of the default character set. +- **Exit**: the saved border and background colours are restored, interrupts re-enabled, and the screen cleared through the KERNAL (`$FFD2`) before `RTS` returns to BASIC. + +## Memory map + +``` +$1001 .. $100C BASIC stub (10 SYS 4109) +$100D .. ... machine code + texts + variables +$0C00 .. $0FE7 screen RAM (written directly) +$0800 .. $0BE7 colour RAM +$FF15 / $FF19 background / border colour (TED) +$FF1D raster line (frame sync) +$FD30 / $FF08 keyboard column select / row latch +``` + +## Pipeline + +`.woodpecker.yaml` is a one-line marker (`platform: plus4`); the pipeline itself is served by the update server. See [plus4-build](https://git.teletypegames.org/build/plus4-build) for the toolchain and the platform page on the wiki: https://wiki.teletypegames.org/development/plus4 diff --git a/main.asm b/main.asm new file mode 100644 index 0000000..681dca4 --- /dev/null +++ b/main.asm @@ -0,0 +1,184 @@ +; ----------------------------------------------------------- +; main.asm - Plus/4 demo: the Teletype Games title card +; a colour wash runs across the title, the prompt blinks, +; any key returns to BASIC +; all timed by polling the TED raster beam +; +; Build: acme -f cbm -o plus4-demo.prg main.asm +; Run: xplus4 plus4-demo.prg +; ----------------------------------------------------------- + +!cpu 6502 +!to "plus4-demo.prg", cbm + +; ---- TED registers ---- +TED_BG = $ff15 ; background colour +TED_BORD = $ff19 ; border colour +TED_RASTL = $ff1d ; raster line, low 8 bits +TED_KBD = $ff08 ; keyboard row latch (write: latch, read: rows) +KBD_COLS = $fd30 ; keyboard column select (0 = selected) + +SCREEN = $0c00 ; screen RAM (40x25 chars) +COLRAM = $0800 ; colour RAM: luminance << 4 | hue + +CHROUT = $ffd2 ; KERNAL: print a character + +; ---- colours (luminance in the high nibble) ---- +BLACK = $00 +DARK_BLUE = $16 +CYAN = $53 +WHITE = $71 +GOLD = $47 +SYNC_LINE = $cc ; raster line the frame loop waits for + +; ---- layout ---- +TITLE_ROW = 8 +TITLE_COL = 13 +PROMPT_ROW = 20 +PROMPT_COL = 13 + +; print a fixed text with one colour +!macro prtext .row, .col, .color, .src, .len { + ldx #0 +- lda .src, x + sta SCREEN + .row * 40 + .col, x + lda #.color + sta COLRAM + .row * 40 + .col, x + inx + cpx #.len + bne - +} + +* = $1001 + +; ---- BASIC stub: 10 SYS 4109 ---- +; so after LOAD"*",8,1 it's enough to type RUN +!byte $0b, $10 ; address of next line +!byte $0a, $00 ; line number: 10 +!byte $9e ; SYS token +!text "4109" ; = $100d +!byte $00 ; end of line +!byte $00, $00 ; end of program + +start: ; = $100d + lda #$93 ; clear the screen through the KERNAL: this also + jsr CHROUT ; parks the cursor before the IRQ is stopped + sei ; no KERNAL IRQ - raster polling must not jitter + + lda TED_BG + sta saved_bg + lda TED_BORD + sta saved_bord + lda #DARK_BLUE + sta TED_BG + lda #BLACK + sta TED_BORD + + +prtext TITLE_ROW, TITLE_COL, WHITE, txt_title, TITLE_LEN + +prtext 10, 14, CYAN, txt_sub, SUB_LEN + +prtext 14, 8, GOLD, txt_line1, LINE1_LEN + +prtext 15, 6, GOLD, txt_line2, LINE2_LEN + +prtext PROMPT_ROW, PROMPT_COL, WHITE, txt_prompt, PROMPT_LEN + + lda #0 + sta framectr + +- jsr check_key ; the RETURN that started us may still be down + bne - + +; ============================================================ +; main loop - one pass per frame +; ============================================================ +main: + jsr wait_frame + inc framectr + + ; colour wash: the ramp slides one cell every second frame + lda framectr + lsr + sta wash_base + ldx #0 +- txa + clc + adc wash_base + and #$0f + tay + lda ramp, y + sta COLRAM + TITLE_ROW * 40 + TITLE_COL, x + inx + cpx #TITLE_LEN + bne - + + ; blinking prompt: hidden while bit 5 of the frame counter is set + lda framectr + and #$20 + beq + + lda #DARK_BLUE + bne ++ ++ lda #WHITE +++ ldx #0 +- sta COLRAM + PROMPT_ROW * 40 + PROMPT_COL, x + inx + cpx #PROMPT_LEN + bne - + + jsr check_key + beq main + +; ---- back to BASIC ---- + lda saved_bg + sta TED_BG + lda saved_bord + sta TED_BORD + cli + lda #$93 + jsr CHROUT + rts + +; ============================================================ +; wait_frame - wait for the raster beam to pass SYNC_LINE once +; ============================================================ +wait_frame: +- lda TED_RASTL + cmp #SYNC_LINE + bne - +- lda TED_RASTL + cmp #SYNC_LINE + beq - + rts + +; ============================================================ +; check_key - Z set when no key is pressed +; all columns selected, joysticks deselected, rows latched +; ============================================================ +check_key: + lda #$00 + sta KBD_COLS + lda #$ff + sta TED_KBD + lda TED_KBD + cmp #$ff + rts + +; ============================================================ +; data +; ============================================================ +ramp: ; gold rising to white and back, 16 steps + !byte $17, $27, $37, $47, $57, $67, $77, $71 + !byte $71, $77, $67, $57, $47, $37, $27, $17 + +txt_title: !scr "teletype games" +TITLE_LEN = * - txt_title +txt_sub: !scr "plus/4 demo" +SUB_LEN = * - txt_sub +txt_line1: !scr "6502 assembly with acme" +LINE1_LEN = * - txt_line1 +txt_line2: !scr "released through warpengine" +LINE2_LEN = * - txt_line2 +txt_prompt: !scr "press any key" +PROMPT_LEN = * - txt_prompt + +framectr: !byte 0 +wash_base: !byte 0 +saved_bg: !byte 0 +saved_bord: !byte 0 diff --git a/metadata.json b/metadata.json new file mode 100644 index 0000000..ec602f5 --- /dev/null +++ b/metadata.json @@ -0,0 +1,10 @@ +{ + "name": "plus4-demo", + "title": "Teletype Games Plus/4 Demo", + "author": "Teletype Games", + "desc": "A Commodore Plus/4 title card in 6502 assembly: the Teletype Games title with a gold colour wash driven by the TED raster beam, a blinking prompt, and a clean return to BASIC on any key.", + "site": "https://git.teletypegames.org/demos/plus4-demo", + "repo": "https://git.teletypegames.org/demos/plus4-demo", + "license": "MIT", + "version": "1.0.0" +}