Add Plasma Grid demo and fix lazy loading

This commit is contained in:
Christian Johannesen 2026-10-16 03:08:27 +02:00
parent 2ce697d9d4
commit 5ae4090a61
6 changed files with 288 additions and 4 deletions

View file

@ -1,6 +1,6 @@
# C64 Program Collection # C64 Program Collection
Thirty small, polished programs for the Commodore 64, written in stock Commodore BASIC V2 and 6502 machine code. The collection mixes arcade action, puzzles, simulations, creative tools, music, utilities, and visual effects. Every entry is self-contained and runs on an ordinary PAL or NTSC C64 without a cartridge or BASIC extension. Thirty-one small, polished programs for the Commodore 64, written in stock Commodore BASIC V2 and 6502 machine code. The collection mixes arcade action, puzzles, simulations, creative tools, music, utilities, and visual effects. Every entry is self-contained and runs on an ordinary PAL or NTSC C64 without a cartridge or BASIC extension.
Browse and run the collection at [c64.p20.dev](https://c64.p20.dev). Browse and run the collection at [c64.p20.dev](https://c64.p20.dev).
@ -38,6 +38,7 @@ Browse and run the collection at [c64.p20.dev](https://c64.p20.dev).
| Typing Sprint | 2026-10-16 | Word | Machine Code | `A`-`Z`, Space, `Q` | Race the clock while copying a Commodore-themed phrase. | [ASM](typing-sprint/typing-sprint.asm) | [Run](https://c64.p20.dev/program/typing-sprint) | | Typing Sprint | 2026-10-16 | Word | Machine Code | `A`-`Z`, Space, `Q` | Race the clock while copying a Commodore-themed phrase. | [ASM](typing-sprint/typing-sprint.asm) | [Run](https://c64.p20.dev/program/typing-sprint) |
| PETSCII Aquarium | 2026-10-16 | Simulation | Machine Code | `F`, `1`-`3`, `Q` | Watch a tiny animated aquarium and feed its roaming fish. | [ASM](petscii-aquarium/petscii-aquarium.asm) | [Run](https://c64.p20.dev/program/petscii-aquarium) | | PETSCII Aquarium | 2026-10-16 | Simulation | Machine Code | `F`, `1`-`3`, `Q` | Watch a tiny animated aquarium and feed its roaming fish. | [ASM](petscii-aquarium/petscii-aquarium.asm) | [Run](https://c64.p20.dev/program/petscii-aquarium) |
| Fifteen Puzzle | 2026-10-16 | Puzzle | Machine Code | `W`, `A`, `S`, `D`, `R`, `Q` | Slide numbered tiles until the classic four-by-four board is restored. | [ASM](fifteen-puzzle/fifteen-puzzle.asm) | [Run](https://c64.p20.dev/program/fifteen-puzzle) | | Fifteen Puzzle | 2026-10-16 | Puzzle | Machine Code | `W`, `A`, `S`, `D`, `R`, `Q` | Slide numbered tiles until the classic four-by-four board is restored. | [ASM](fifteen-puzzle/fifteen-puzzle.asm) | [Run](https://c64.p20.dev/program/fifteen-puzzle) |
| Plasma Grid | 2026-10-16 | Demo | Machine Code | `1`-`3`, `C`, Space, `Q` | Fill the screen with a flowing field of color generated in real time. | [ASM](plasma-grid/plasma-grid.asm) | [Run](https://c64.p20.dev/program/plasma-grid) |
## Running the programs ## Running the programs

263
plasma-grid/plasma-grid.asm Normal file
View file

@ -0,0 +1,263 @@
!cpu 6510
; Plasma Grid - real-time character and color RAM animation.
; Uses the collection's standard black splash screen.
; Self-starting BASIC line: 10 SYS2061
* = $0801
!word basic_end
!word 10
!byte $9e
!text "2061"
!byte 0
basic_end: !word 0
* = $080d
SCREEN = $0400
COLOR = $d800
GETIN = $ffe4
CHROUT = $ffd2
start:
lda $d020
sta old_border
lda $d021
sta old_background
lda #0
sta $d020
sta $d021
jsr show_splash
lda #0
sta $d020
sta $d021
lda #147
jsr CHROUT
lda #0
sta phase_x
sta phase_y
sta palette_offset
sta paused
sta frame_count
lda #3
sta animation_speed
lda $a2
sta last_jiffy
jsr render_plasma
main_loop:
jsr GETIN
beq check_clock
cmp #81
bne check_palette
jmp exit
check_palette:
cmp #67
bne check_pause
lda palette_offset
eor #16
sta palette_offset
jmp check_clock
check_pause:
cmp #32
bne check_speed_one
lda paused
eor #1
sta paused
jmp check_clock
check_speed_one:
cmp #49
bne check_speed_two
lda #6
sta animation_speed
jmp check_clock
check_speed_two:
cmp #50
bne check_speed_three
lda #3
sta animation_speed
jmp check_clock
check_speed_three:
cmp #51
bne check_clock
lda #1
sta animation_speed
check_clock:
lda paused
bne main_loop
lda $a2
cmp last_jiffy
beq main_loop
sta last_jiffy
inc frame_count
lda frame_count
cmp animation_speed
bcc main_loop
lda #0
sta frame_count
inc phase_x
lda phase_x
and #63
sta phase_x
lda phase_x
and #1
bne draw_frame
inc phase_y
lda phase_y
and #63
sta phase_y
draw_frame:
jsr render_plasma
jmp main_loop
render_plasma:
lda #0
sta row
row_loop:
ldx row
lda screen_row_lo,x
sta $fb
lda screen_row_hi,x
sta $fc
lda color_row_lo,x
sta $fd
lda color_row_hi,x
sta $fe
lda #0
sta column
column_loop:
lda column
clc
adc phase_x
and #63
tax
lda wave,x
sta wave_sum
lda row
asl
clc
adc phase_y
and #63
tax
lda wave,x
clc
adc wave_sum
and #15
sta pattern
clc
adc palette_offset
tax
lda palettes,x
ldy column
sta ($fd),y
lda pattern
and #4
beq solid_cell
lda #102
bne paint_cell
solid_cell:
lda #160
paint_cell:
sta ($fb),y
inc column
lda column
cmp #40
bne column_loop
inc row
lda row
cmp #25
bne row_loop
rts
show_splash:
ldx #0
print_splash:
lda splash_text,x
beq wait_for_space
jsr CHROUT
inx
bne print_splash
wait_for_space:
jsr GETIN
cmp #32
bne wait_for_space
rts
exit:
lda old_border
sta $d020
lda old_background
sta $d021
lda #147
jsr CHROUT
rts
splash_text:
!byte 147,13,13,13
!byte 154
!text " PLASMA GRID"
!byte 13,13
!byte 5
!text " A FLOWING FIELD OF COLOR AND MOTION"
!byte 13,13,13
!byte 153
!text " CONTROLS"
!byte 13,13
!text " "
!byte 158
!text "1-3"
!byte 5
!text " SPEED "
!byte 158
!text "C"
!byte 5
!text " COLOR "
!byte 158
!text "SPACE"
!byte 5
!text " PAUSE "
!byte 158
!text "Q"
!byte 5
!text " QUIT"
!byte 13,13,13
!byte 5
!text " HTTPS://C64.P20.DEV"
!byte 13,13
!byte 152
!text " RELEASED 16 OCTOBER 2026"
!byte 13,13,13
!byte 158
!text " PRESS SPACE TO START"
!byte 0
; Two deliberately different 16-color sequences.
palettes:
!byte 0,6,11,4,14,3,13,1,13,3,14,4,11,6,0,6
!byte 0,2,8,7,1,7,8,2,9,10,5,13,3,14,6,0
; A 64-step triangle/sine-like wave. Combining horizontal and vertical
; samples produces the moving plasma interference pattern.
wave:
!byte 8,9,10,11,12,13,14,15,15,15,15,14,14,13,12,11
!byte 10,9,8,7,6,5,4,3,2,1,0,0,0,0,1,1
!byte 2,3,4,5,6,7,8,9,10,11,12,13,14,15,15,15
!byte 14,14,13,12,11,10,9,8,7,6,5,4,3,2,1,0
screen_row_lo: !for i,0,24 { !byte <(SCREEN+i*40) }
screen_row_hi: !for i,0,24 { !byte >(SCREEN+i*40) }
color_row_lo: !for i,0,24 { !byte <(COLOR+i*40) }
color_row_hi: !for i,0,24 { !byte >(COLOR+i*40) }
phase_x: !byte 0
phase_y: !byte 0
palette_offset: !byte 0
paused: !byte 0
animation_speed: !byte 0
frame_count: !byte 0
last_jiffy: !byte 0
row: !byte 0
column: !byte 0
wave_sum: !byte 0
pattern: !byte 0
old_border: !byte 0
old_background: !byte 0

BIN
plasma-grid/plasma-grid.prg Normal file

Binary file not shown.

View file

@ -178,5 +178,11 @@
"description": "Slide numbered tiles until the classic four-by-four board is restored.", "description": "Slide numbered tiles until the classic four-by-four board is restored.",
"controls": [{"keys":["W","A","S","D"],"description":"Slide a tile toward the empty space"},{"keys":["R"],"description":"Shuffle the board again"},{"keys":["Q"],"description":"Return to BASIC"}], "controls": [{"keys":["W","A","S","D"],"description":"Slide a tile toward the empty space"},{"keys":["R"],"description":"Shuffle the board again"},{"keys":["Q"],"description":"Return to BASIC"}],
"path": "fifteen-puzzle/fifteen-puzzle.prg", "source": "fifteen-puzzle/fifteen-puzzle.asm" "path": "fifteen-puzzle/fifteen-puzzle.prg", "source": "fifteen-puzzle/fifteen-puzzle.asm"
},
{
"title": "Plasma Grid", "category": "Demo", "language": "machine-code", "releaseDate": "2026-10-16",
"description": "Fill the screen with a flowing field of color generated in real time.",
"controls": [{"keys":["1","2","3"],"description":"Select slow, medium, or fast animation"},{"keys":["C"],"description":"Change color palette"},{"keys":["SPACE"],"description":"Pause or resume the animation"},{"keys":["Q"],"description":"Return to BASIC"}],
"path": "plasma-grid/plasma-grid.prg", "source": "plasma-grid/plasma-grid.asm"
} }
] ]

View file

@ -23,6 +23,7 @@ let allPrograms = [];
let filteredPrograms = []; let filteredPrograms = [];
let visibleProgramCount = 0; let visibleProgramCount = 0;
const PROGRAM_BATCH_SIZE = 12; const PROGRAM_BATCH_SIZE = 12;
let programObserver;
const categoryMeta = { const categoryMeta = {
Arcade: '🎮', Creative: '✎', Demo: '✦', Dice: '⚄', Educational: '⌁', Arcade: '🎮', Creative: '✎', Demo: '✦', Dice: '⚄', Educational: '⌁',
@ -138,6 +139,16 @@ function appendProgramBatch() {
status.textContent = `${visibleProgramCount} of ${filteredPrograms.length} matching programs shown · ${allPrograms.length} total`; status.textContent = `${visibleProgramCount} of ${filteredPrograms.length} matching programs shown · ${allPrograms.length} total`;
} }
function observeProgramSentinel() {
programObserver.disconnect();
if (!programSentinel.hidden) requestAnimationFrame(() => programObserver.observe(programSentinel));
}
function loadBatchNearViewport() {
if (programSentinel.hidden || visibleProgramCount >= filteredPrograms.length) return;
if (programSentinel.getBoundingClientRect().top <= window.innerHeight + 500) appendProgramBatch();
}
function renderPrograms() { function renderPrograms() {
const query = searchInput.value.trim().toLowerCase(); const query = searchInput.value.trim().toLowerCase();
const selectedCategory = categoryFilter.value; const selectedCategory = categoryFilter.value;
@ -156,6 +167,7 @@ function renderPrograms() {
programsElement.replaceChildren(); programsElement.replaceChildren();
visibleProgramCount = 0; visibleProgramCount = 0;
appendProgramBatch(); appendProgramBatch();
observeProgramSentinel();
} }
async function showSource(program) { async function showSource(program) {
@ -295,10 +307,11 @@ window.addEventListener('popstate', () => {
} }
}); });
const programObserver = new IntersectionObserver(entries => { programObserver = new IntersectionObserver(entries => {
if (entries.some(entry => entry.isIntersecting) && visibleProgramCount < filteredPrograms.length) appendProgramBatch(); if (entries.some(entry => entry.isIntersecting) && visibleProgramCount < filteredPrograms.length) appendProgramBatch();
}, { rootMargin: '500px 0px' }); }, { rootMargin: '500px 0px' });
programObserver.observe(programSentinel); window.addEventListener('scroll', loadBatchNearViewport, { passive: true });
window.addEventListener('resize', loadBatchNearViewport);
function setView(view) { function setView(view) {
const list = view === 'list'; const list = view === 'list';

View file

@ -11,7 +11,8 @@ for source in \
jiffy-clock/jiffy-clock.asm \ jiffy-clock/jiffy-clock.asm \
typing-sprint/typing-sprint.asm \ typing-sprint/typing-sprint.asm \
petscii-aquarium/petscii-aquarium.asm \ petscii-aquarium/petscii-aquarium.asm \
fifteen-puzzle/fifteen-puzzle.asm fifteen-puzzle/fifteen-puzzle.asm \
plasma-grid/plasma-grid.asm
do do
output="${source%.asm}.prg" output="${source%.asm}.prg"
echo "Assembling $source -> $output" echo "Assembling $source -> $output"