I've written a lot of 6502 assembly code for this challenge. Without testing this on a real Apple II, I don't know if the timing is quite right. KEGSMAC and Virtual ][ seem to be different by about 65 cycles (one line.)
*1195:30 Virtual ][
*30-23 difference between delay loop parameters
=0D 13 * 5 cycles = 65 cycles
Here are the 6502 assembly routines that I've ended up with at the end of this challenge:
And, this is a demo written in BASIC:
vbl.s : REM ORG $1100, 153 bytes and expands to 250 bytes with self modifying code, called by FRAME, no parameters.
creates a 256 byte table of byte counts at $1000.$10FF
This is the Vertical BLank detection routine. It non-destructively finds/waits for the start of the vertical blank. It uses HGR: high-resolution (280x192) page 1. It finds a suitable byte to wait for on the floating bus. If the byte is in use on HGR, it temporarily modifies the bytes, waits on the floating bus, and then restores the bytes back to HGR. After reading some of chapters in Sather's book, I found out that my original VBL routine won't work because many addresses on the screen are scanned more than once. I settled on putting the byte to wait on in address $27D0 which is only scanned once. The value $00 seems to show up on the floating bus (in KEGSMAC) even if there are no zeroes in the screen memory. There is a delay at the end of the detection routine, so that the time is at the start of the vertical blank, just past the 192nd line on the screen.
frame.s CALL 3072 : REM ORG $0C00, 239 bytes, needs lookup tables set up, calls VBL, parameters:
IPTRLO EQU $06
IPTRHI EQU $07
ICNTLO EQU $08
ICNTHI EQU $09
The address that $06.07 points to is the first frame of the animation.
Each frame in the list uses a one byte pointer.
$00 points to $6000
$01 points to $6040
$02 points to $6080, and so on...
You can repeat frames in the list.
This is useful for slowing down the animation, or reusing frames.
These are just pointers, so they can be in any order.
It animates through ICNT + 1 frames, and repeats until a key is pressed.
Each byte in the 64 byte table contains three values.
The values from 0 to 5 indicating the graphics mode
to use for each line on the screen: 3 * 64 is 192 lines.
creates a 3 byte index lookup: $DD8.$DD9
This routine allows the setting of graphics modes line by line, and can also animate through various frames at 60 frames per second. A single frame can be used to mixed various graphics modes and add more color. The frame animation was difficult to test using emulators. There is a delay during the VBL period. All of the code in this routine is time critical. I even used "LDA: $0007" to tell the Merlin32 assembler to output a 4 cycle absolute addressing instruction when accessing the zero page because the regular 3 cycle "LDA $07" zero page addressing would have come up one cycle short.
lookup.s CALL 4643 : REM ORG $1223, 45 bytes, no parameters
creates three 216 byte (6^3) lookup tables: $D00.$DD7 $E00.$ED7 $F00.$FD7
This builds lookup tables for the line by line modes. Three line modes are stored in one byte of memory. I needed to compact the frames, and managed to reduce the memory footprint using these lookup tables.
fill.s CALL 4608 : REM ORG $1200, 35 bytes, parameters:
STARTHI EQU $EC
ENDHI EQU $ED
COLOR1 EQU $EE
;COLOR2 EQU $EF
This is a utility routine for filling patterns of bytes in memory. It's useful for creating the frames, and filling screens with patterns.
Alternating GR and GR2 with 8 pixels of TEXT in the middle.
6 POKE 6,7 : REM lo-order address of frame table
7 POKE 7,0 : REM hi-order address of frame table
8 POKE 8,0 : REM lo-order number of frames minus one
9 POKE 9,0 : REM hi-order number of frames minus one
20 B0 = 3:B1 = 2:B2 = 3 : REM three lines of alternating GR and GR2
30 GOSUB 600:C1 = B
40 B0 = 2:B1 = 3:B2 = 2 : REM three lines of alternating GR2 and GR
50 GOSUB 600:C2 = B
60 POKE 236,96: POKE 237,97 : REM $6000 (24576) frame start, $40 (64) bytes in size
70 GOSUB 500
80 V = 12
90 B0 = 2:B1 = 4:B2 = 4 : REM bottom 2 lines text
91 GOSUB 600
92 POKE 24576 + 29,B
93 B0 = 4: GOSUB 600 : REM 6 more lines of text
94 POKE 24576 + 30,B
96 POKE 24576 + 31,B
100 POKE 236,4: POKE 237,8 : REM fill GR with a dark colored pattern
101 C1 = 1 + 2 * 16
102 C2 = 4 + 8 * 16
120 GOSUB 500
130 POKE 236,8: POKE 237,12 : REM fill GR2 with a light colored pattern
131 C1 = 11 + 7 * 16
132 C2 = 14 + 13 * 16
150 GOSUB 500
200 VTAB V: HTAB 1
210 PRINT " 2015 WINTER ";
211 PRINT "WARMUP - VERT";
212 PRINT "ICAL BLANKING ";
400 CALL 3072: END : REM call the (vbl, and) frame routine
500 POKE 238,C1: POKE 239,C2
510 CALL 4608: RETURN : REM call the fill routine
600 B = B0 * 6 ^ 0
610 B = B1 * 6 ^ 1 + B
620 B = B2 * 6 ^ 2 + B: RETURN