@8bitscript/c64 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/native/6502/raster.s +121 -0
- package/package.json +396 -0
- package/src/bitmap.8bs +175 -0
- package/src/charset.8bs +142 -0
- package/src/geometry.8bs +116 -0
- package/src/index.8bs +427 -0
- package/src/input.8bs +269 -0
- package/src/joystick.8bs +78 -0
- package/src/keyboard.8bs +84 -0
- package/src/keys.8bs +103 -0
- package/src/mouse.8bs +265 -0
- package/src/pointer.8bs +183 -0
- package/src/random.8bs +81 -0
- package/src/raster.8bs +190 -0
- package/src/reu.8bs +210 -0
- package/src/screen.8bs +120 -0
- package/src/scroll.8bs +130 -0
- package/src/sid.8bs +211 -0
- package/src/sprites.8bs +197 -0
- package/src/text.8bs +174 -0
package/src/bitmap.8bs
ADDED
|
@@ -0,0 +1,175 @@
|
|
|
1
|
+
// @8bitscript/c64/bitmap — the VIC-II's bitmap modes: 320x200 pixels, or
|
|
2
|
+
// 160x200 in four colours.
|
|
3
|
+
//
|
|
4
|
+
// Named by "8bitscript".exports["./bitmap"] in this package's package.json:
|
|
5
|
+
//
|
|
6
|
+
// import { bitmap } from "@8bitscript/c64/bitmap";
|
|
7
|
+
//
|
|
8
|
+
// bitmap.enter(false); // hi-res: 320x200, two colours a cell
|
|
9
|
+
// bitmap.clear();
|
|
10
|
+
// bitmap.fillColors(1, 0); // white on black, every cell
|
|
11
|
+
// for (let x: usmallint = 0; x < 320; x++) {
|
|
12
|
+
// bitmap.plot(x, x >> 1); // a line
|
|
13
|
+
// }
|
|
14
|
+
// ...
|
|
15
|
+
// bitmap.leave(); // text mode again
|
|
16
|
+
//
|
|
17
|
+
// Hardware-level, C64-only surface, the layer a portable canvas
|
|
18
|
+
// capability (docs/systems.md's bitmap rung) will sit on. The bitmap is
|
|
19
|
+
// 8000 bytes at $E000, laid out the VIC's way: 8 bytes a cell (one a
|
|
20
|
+
// pixel row), 40 cells a row, 25 rows — so pixel (x, y) is bit `7 - x % 8`
|
|
21
|
+
// of byte `(y / 8) * 320 + (x / 8) * 8 + y % 8`, which `offset` computes
|
|
22
|
+
// with a row table and masks, no multiply. Where the picture is, and
|
|
23
|
+
// why the colour matrix is under the I/O area, is ./geometry.8bs's story
|
|
24
|
+
// (the `Bitmap` namespace).
|
|
25
|
+
//
|
|
26
|
+
// ---- colours ----------------------------------------------------------------
|
|
27
|
+
//
|
|
28
|
+
// Hi-res: a set bit shows the cell's high nybble, a clear bit its low
|
|
29
|
+
// nybble, both from the colour matrix — sixteen colours, two per 8x8
|
|
30
|
+
// cell. Multicolour: pixels are pairs of bits, twice as wide (x 0-159),
|
|
31
|
+
// %00 the background register ($D021, screen.setBackground), %01 the
|
|
32
|
+
// matrix's high nybble, %10 its low nybble, %11 the cell's colour RAM
|
|
33
|
+
// nybble — four colours a cell, three of them per cell.
|
|
34
|
+
//
|
|
35
|
+
// The colour matrix is RAM under the I/O area, so a matrix write is a
|
|
36
|
+
// window with I/O banked out (writeUnderIo in ./index.8bs): `setCellColors`
|
|
37
|
+
// is one such window, `fillColors` is one window for all thousand cells —
|
|
38
|
+
// about 15000 cycles, most of a frame — and with the raster interrupt on,
|
|
39
|
+
// that frame's entries wait for it. Colour RAM is I/O and written freely.
|
|
40
|
+
//
|
|
41
|
+
// ---- sprites over a bitmap --------------------------------------------------
|
|
42
|
+
//
|
|
43
|
+
// The sprite pointers move with the screen matrix, to $DFF8 under the I/O
|
|
44
|
+
// area; sprites.setShape() knows (it reads `videoMode`). The shape blocks
|
|
45
|
+
// at $E400 are the bitmap now, so a sprite over a bitmap draws from blocks
|
|
46
|
+
// 96-111 at $D800 (Bitmap.SHAPE_BLOCK_FIRST/SHAPE_COUNT), written with
|
|
47
|
+
// sprites.setShapeByte(), which writes under the I/O area for those.
|
|
48
|
+
//
|
|
49
|
+
// ---- text and bitmap ----------------------------------------------------------
|
|
50
|
+
//
|
|
51
|
+
// One or the other. `enter` switches the VIC to the bitmap over the same
|
|
52
|
+
// $E000 RAM the text screen used, and `leave` switches back — the text is
|
|
53
|
+
// gone, the bitmap is what the screen matrix now holds (screen.blank()
|
|
54
|
+
// clears it). @8bitscript/text writes screen codes into the bitmap while
|
|
55
|
+
// this mode is on; nothing stops it, and it draws noise.
|
|
56
|
+
import {
|
|
57
|
+
control1, control2, memoryPointer, setupVideo, videoMode, VideoMode,
|
|
58
|
+
bankIoOut, bankIoIn, writeUnderIo,
|
|
59
|
+
} from "./index.8bs";
|
|
60
|
+
import { Video, Bitmap, bitmapRam, colorRam } from "./geometry.8bs";
|
|
61
|
+
|
|
62
|
+
// Byte offset of each cell row's first byte: row * 320.
|
|
63
|
+
const ROW_OFFSET: array<usmallint, 25> = [
|
|
64
|
+
0, 320, 640, 960, 1280, 1600, 1920, 2240, 2560, 2880, 3200, 3520, 3840,
|
|
65
|
+
4160, 4480, 4800, 5120, 5440, 5760, 6080, 6400, 6720, 7040, 7360, 7680,
|
|
66
|
+
];
|
|
67
|
+
// Hi-res: the bit of pixel x % 8, left to right.
|
|
68
|
+
const BIT: array<utinyint, 8> = [128, 64, 32, 16, 8, 4, 2, 1];
|
|
69
|
+
const NOT_BIT: array<utinyint, 8> = [127, 191, 223, 239, 247, 251, 253, 254];
|
|
70
|
+
// Multicolour: the two bits of pixel x % 4, and a colour 0-3 shifted into them
|
|
71
|
+
// (index (x % 4) * 4 + colour).
|
|
72
|
+
const PAIR_MASK: array<utinyint, 4> = [0x3F, 0xCF, 0xF3, 0xFC];
|
|
73
|
+
const PAIR_BITS: array<utinyint, 16> = [
|
|
74
|
+
0x00, 0x40, 0x80, 0xC0,
|
|
75
|
+
0x00, 0x10, 0x20, 0x30,
|
|
76
|
+
0x00, 0x04, 0x08, 0x0C,
|
|
77
|
+
0x00, 0x01, 0x02, 0x03,
|
|
78
|
+
];
|
|
79
|
+
const PAIR_SHIFT: array<utinyint, 4> = [6, 4, 2, 0];
|
|
80
|
+
|
|
81
|
+
export namespace bitmap {
|
|
82
|
+
const WIDTH: usmallint = 320; // hi-res pixels across
|
|
83
|
+
const HEIGHT: utinyint = 200;
|
|
84
|
+
const MULTICOLOR_WIDTH: utinyint = 160; // multicolour pixels across
|
|
85
|
+
const BYTES: usmallint = 8000;
|
|
86
|
+
|
|
87
|
+
// Bitmap mode on, hi-res or multicolour, over whatever the RAM holds.
|
|
88
|
+
function enter(multicolor: bool): void {
|
|
89
|
+
setupVideo();
|
|
90
|
+
videoMode = VideoMode.BITMAP;
|
|
91
|
+
// Bits 0-4 (YSCROLL, RSEL, DEN) kept, ECM off, BMM on, and bit 7 —
|
|
92
|
+
// the raster compare's ninth bit on a write — clear.
|
|
93
|
+
control1 = (control1 & 0x1F) | 0x20;
|
|
94
|
+
if (multicolor) {
|
|
95
|
+
control2 = control2 | 0x10;
|
|
96
|
+
} else {
|
|
97
|
+
control2 = control2 & 0xEF;
|
|
98
|
+
}
|
|
99
|
+
memoryPointer = Bitmap.MEMORY_POINTER;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
// Text mode again: the screen matrix at $E000 over the upper-case set.
|
|
103
|
+
function leave(): void {
|
|
104
|
+
control1 = control1 & 0x1F;
|
|
105
|
+
control2 = control2 & 0xEF;
|
|
106
|
+
memoryPointer = Video.MEMORY_POINTER_UPPERCASE;
|
|
107
|
+
videoMode = VideoMode.TEXT;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
// Every pixel clear (`bits` 0) or set (255), or any pattern byte.
|
|
111
|
+
function clear(bits: utinyint = 0): void {
|
|
112
|
+
for (let i: usmallint = 0; i < Bitmap.BYTES; i++) {
|
|
113
|
+
bitmapRam[i] = bits;
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
// The byte holding hi-res pixel (x, y): see the header.
|
|
118
|
+
function offset(x: usmallint, y: utinyint): usmallint {
|
|
119
|
+
return ROW_OFFSET[y >> 3] + (x & 0x1F8) + (y & 7);
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
function plot(x: usmallint, y: utinyint): void {
|
|
123
|
+
let at: usmallint = bitmap.offset(x, y);
|
|
124
|
+
bitmapRam[at] = bitmapRam[at] | BIT[x & 7];
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
function unplot(x: usmallint, y: utinyint): void {
|
|
128
|
+
let at: usmallint = bitmap.offset(x, y);
|
|
129
|
+
bitmapRam[at] = bitmapRam[at] & NOT_BIT[x & 7];
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
function point(x: usmallint, y: utinyint): bool {
|
|
133
|
+
return (bitmapRam[bitmap.offset(x, y)] & BIT[x & 7]) != 0;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
// Multicolour pixel (x 0-159, y) set to `color` 0-3 (see the header).
|
|
137
|
+
function plotColor(x: utinyint, y: utinyint, color: utinyint): void {
|
|
138
|
+
let at: usmallint = ROW_OFFSET[y >> 3] + ((x & 0xFC) << 1) + (y & 7);
|
|
139
|
+
let pair: utinyint = x & 3;
|
|
140
|
+
bitmapRam[at] = (bitmapRam[at] & PAIR_MASK[pair]) | PAIR_BITS[pair * 4 + (color & 3)];
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
// The colour 0-3 of multicolour pixel (x, y).
|
|
144
|
+
function pointColor(x: utinyint, y: utinyint): utinyint {
|
|
145
|
+
let at: usmallint = ROW_OFFSET[y >> 3] + ((x & 0xFC) << 1) + (y & 7);
|
|
146
|
+
return (bitmapRam[at] >> PAIR_SHIFT[x & 3]) & 3;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
// Cell `cell` (row * 40 + column): the colour of set bits and of clear
|
|
150
|
+
// bits (hi-res), or of %01 and %10 pixels (multicolour).
|
|
151
|
+
function setCellColors(cell: usmallint, foreground: utinyint, background: utinyint): void {
|
|
152
|
+
writeUnderIo(Bitmap.MATRIX + cell, (foreground << 4) | (background & 15));
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
// The same pair for every cell, in one window.
|
|
156
|
+
function fillColors(foreground: utinyint, background: utinyint): void {
|
|
157
|
+
let pair: utinyint = (foreground << 4) | (background & 15);
|
|
158
|
+
bankIoOut();
|
|
159
|
+
for (let cell: usmallint = 0; cell < Video.CELL_COUNT; cell++) {
|
|
160
|
+
memory.write(Bitmap.MATRIX + cell, pair);
|
|
161
|
+
}
|
|
162
|
+
bankIoIn();
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
// Multicolour's third colour per cell (%11), from colour RAM.
|
|
166
|
+
function setCellColor3(cell: usmallint, color: utinyint): void {
|
|
167
|
+
colorRam[cell] = color & 15;
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
function fillColor3(color: utinyint): void {
|
|
171
|
+
for (let cell: usmallint = 0; cell < Video.CELL_COUNT; cell++) {
|
|
172
|
+
colorRam[cell] = color & 15;
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
}
|
package/src/charset.8bs
ADDED
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
// @8bitscript/c64/charset — the character set is RAM: redefine it.
|
|
2
|
+
//
|
|
3
|
+
// Named by "8bitscript".exports["./charset"] in this package's package.json:
|
|
4
|
+
//
|
|
5
|
+
// import { charset } from "@8bitscript/c64/charset";
|
|
6
|
+
//
|
|
7
|
+
// // Screen code 1 ('A' as text prints it) becomes a solid block with a hole.
|
|
8
|
+
// charset.define(1, 0xFF, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0xFF);
|
|
9
|
+
// charset.copy(2, 1); // and code 2 the same
|
|
10
|
+
// charset.restore(); // the ROM's glyphs again
|
|
11
|
+
//
|
|
12
|
+
// The set the VIC draws text from is this package's copy of the character
|
|
13
|
+
// ROM in the RAM under the I/O area ($D000, ./geometry.8bs), and a copy is
|
|
14
|
+
// writable: a program's own tiles, a font, a map's terrain are glyphs
|
|
15
|
+
// written over the ROM's, each 8 bytes — one byte a pixel row, bit 7 the
|
|
16
|
+
// left pixel — at `Video.CHARSET + 8 * code`. The upper-case set (256
|
|
17
|
+
// glyphs at $D000-$D7FF) is the one @8bitscript/text selects on every
|
|
18
|
+
// print, so it is the one to define into: codes 1-26 are what text prints
|
|
19
|
+
// for 'A'-'Z', 32-63 the digits and punctuation, 64-127 the graphics, and
|
|
20
|
+
// 128-255 the same in reverse video. A program that never prints text is
|
|
21
|
+
// free to use all 256.
|
|
22
|
+
//
|
|
23
|
+
// Every write here is a window with the I/O area banked out (./index.8bs,
|
|
24
|
+
// writeUnderIo): `define` is one window for a glyph's 8 bytes, `copy`
|
|
25
|
+
// one for 8 reads and 8 writes. With the raster interrupt on, each window
|
|
26
|
+
// is a few dozen cycles that frame's entries wait for; `restore` is a
|
|
27
|
+
// second ROM copy (about four frames of window) — do it before
|
|
28
|
+
// raster.enable(), or accept the frame.
|
|
29
|
+
//
|
|
30
|
+
// Multicolour text ($D016 bit 4, `charset.setMulticolor`): in a cell whose
|
|
31
|
+
// colour RAM nybble has bit 3 set, pixels are pairs — %00 the background,
|
|
32
|
+
// %01 $D022, %10 $D023, %11 the low three bits of the cell's colour — and
|
|
33
|
+
// a cell without bit 3 is drawn hi-res in that colour, so one screen mixes
|
|
34
|
+
// both. `useLowercase` selects the ROM's other set at $D800 for a program
|
|
35
|
+
// that wants it; @8bitscript/text's next print selects upper-case again.
|
|
36
|
+
import {
|
|
37
|
+
control2, memoryPointer, backgroundColor1, backgroundColor2,
|
|
38
|
+
setupVideo, copyCharacterRom, videoMode, VideoMode, bankIoOut, bankIoIn, readUnderIo,
|
|
39
|
+
} from "./index.8bs";
|
|
40
|
+
import { Video } from "./geometry.8bs";
|
|
41
|
+
|
|
42
|
+
export namespace charset {
|
|
43
|
+
const COUNT: usmallint = 256;
|
|
44
|
+
const BYTES: utinyint = 8; // a glyph: one byte a row
|
|
45
|
+
|
|
46
|
+
// Where glyph `code`'s 8 bytes start, in the upper-case set.
|
|
47
|
+
function offset(code: utinyint): usmallint {
|
|
48
|
+
return Video.CHARSET + code * 8;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
// Glyph `code` becomes the eight rows given, top to bottom.
|
|
52
|
+
function define(code: utinyint, row0: utinyint, row1: utinyint, row2: utinyint, row3: utinyint, row4: utinyint, row5: utinyint, row6: utinyint, row7: utinyint): void {
|
|
53
|
+
setupVideo();
|
|
54
|
+
let at: usmallint = charset.offset(code);
|
|
55
|
+
bankIoOut();
|
|
56
|
+
memory.write(at, row0);
|
|
57
|
+
memory.write(at + 1, row1);
|
|
58
|
+
memory.write(at + 2, row2);
|
|
59
|
+
memory.write(at + 3, row3);
|
|
60
|
+
memory.write(at + 4, row4);
|
|
61
|
+
memory.write(at + 5, row5);
|
|
62
|
+
memory.write(at + 6, row6);
|
|
63
|
+
memory.write(at + 7, row7);
|
|
64
|
+
bankIoIn();
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
// One row of glyph `code`.
|
|
68
|
+
function setRow(code: utinyint, row: utinyint, bits: utinyint): void {
|
|
69
|
+
setupVideo();
|
|
70
|
+
bankIoOut();
|
|
71
|
+
memory.write(charset.offset(code) + (row & 7), bits);
|
|
72
|
+
bankIoIn();
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
// Read one row back.
|
|
76
|
+
function readRow(code: utinyint, row: utinyint): utinyint {
|
|
77
|
+
setupVideo();
|
|
78
|
+
return readUnderIo(charset.offset(code) + (row & 7));
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
// Glyph `target` becomes a copy of glyph `source`.
|
|
82
|
+
function copy(target: utinyint, source: utinyint): void {
|
|
83
|
+
setupVideo();
|
|
84
|
+
let sourceAt: usmallint = charset.offset(source);
|
|
85
|
+
let targetAt: usmallint = charset.offset(target);
|
|
86
|
+
bankIoOut();
|
|
87
|
+
for (let i: utinyint = 0; i < 8; i++) {
|
|
88
|
+
memory.write(targetAt + i, memory.read(sourceAt + i));
|
|
89
|
+
}
|
|
90
|
+
bankIoIn();
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
// Every row of glyph `code` the same byte: 0 blank, 255 solid.
|
|
94
|
+
function fill(code: utinyint, bits: utinyint): void {
|
|
95
|
+
setupVideo();
|
|
96
|
+
let at: usmallint = charset.offset(code);
|
|
97
|
+
bankIoOut();
|
|
98
|
+
for (let i: utinyint = 0; i < 8; i++) {
|
|
99
|
+
memory.write(at + i, bits);
|
|
100
|
+
}
|
|
101
|
+
bankIoIn();
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
// The ROM's glyphs again, both sets — all 4K under the I/O area, so
|
|
105
|
+
// in bitmap mode this also overwrites the colour matrix and the shape
|
|
106
|
+
// blocks that live there: leave bitmap mode first.
|
|
107
|
+
function restore(): void {
|
|
108
|
+
setupVideo();
|
|
109
|
+
copyCharacterRom();
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
// Multicolour text mode on or off (see the header).
|
|
113
|
+
function setMulticolor(on: bool): void {
|
|
114
|
+
if (on) {
|
|
115
|
+
control2 = control2 | 0x10;
|
|
116
|
+
} else {
|
|
117
|
+
control2 = control2 & 0xEF;
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
// The two shared multicolour text colours ($D022, $D023).
|
|
122
|
+
function setSharedColors(color1: utinyint, color2: utinyint): void {
|
|
123
|
+
backgroundColor1 = color1 & 15;
|
|
124
|
+
backgroundColor2 = color2 & 15;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
// The ROM's lower-case set (text mode only; the next text print
|
|
128
|
+
// selects upper-case again).
|
|
129
|
+
function useLowercase(): void {
|
|
130
|
+
setupVideo();
|
|
131
|
+
if (videoMode == VideoMode.TEXT) {
|
|
132
|
+
memoryPointer = Video.MEMORY_POINTER_LOWERCASE;
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
function useUppercase(): void {
|
|
137
|
+
setupVideo();
|
|
138
|
+
if (videoMode == VideoMode.TEXT) {
|
|
139
|
+
memoryPointer = Video.MEMORY_POINTER_UPPERCASE;
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
}
|
package/src/geometry.8bs
ADDED
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
// @8bitscript/c64 — where the picture lives: VIC bank 3, above the program.
|
|
2
|
+
//
|
|
3
|
+
// The VIC-II sees one 16K bank at a time, and everything it draws from —
|
|
4
|
+
// the screen matrix, the character set, the sprite shapes, the eight
|
|
5
|
+
// sprite pointers — has to be inside that one bank. The SDK links a
|
|
6
|
+
// program into $0801-$CFFF (mos-platform/c64/lib/link.ld: `ram` is
|
|
7
|
+
// ORIGIN $0801, LENGTH $C7FF, and the C stack grows down from $D000), and
|
|
8
|
+
// it decides where every byte of code and data goes. Nothing the VIC
|
|
9
|
+
// needs can be promised a place inside that region without a build-time
|
|
10
|
+
// overlap check the toolchain does not have, so the picture lives in the
|
|
11
|
+
// only RAM the linker never touches: $D000-$FFFF, the upper half of VIC
|
|
12
|
+
// bank 3 ($C000-$FFFF). The VIC always reads RAM there — its two
|
|
13
|
+
// character-ROM windows are in banks 0 and 2 ($1000 and $9000), not here —
|
|
14
|
+
// and the CPU writes RAM there too: a store to $E000-$FFFF lands under the
|
|
15
|
+
// KERNAL ROM whether the ROM is mapped or not.
|
|
16
|
+
//
|
|
17
|
+
// $D000-$DFFF the character ROM's 4K, copied into the RAM under the I/O
|
|
18
|
+
// area once at start-up (setupVideo() in ./index.8bs): the
|
|
19
|
+
// upper-case/graphics set at $D000, lower-case at $D800.
|
|
20
|
+
// The same offset ($1000) in the bank as the ROM window has
|
|
21
|
+
// in bank 0, so $D018's charset field is the ROM's own.
|
|
22
|
+
// $E000-$E3E7 the 40x25 screen matrix, one screen code per cell.
|
|
23
|
+
// $E3F8-$E3FF the eight sprite pointers (screen + $3F8, a VIC rule).
|
|
24
|
+
// $E400-$FFBF 111 sprite shape blocks of 64 bytes, blocks 144-254 of
|
|
25
|
+
// the bank. Block 255 ($FFC0-$FFFF) is RAM too, but its
|
|
26
|
+
// last six bytes are the CPU's NMI/reset/IRQ vectors — RAM
|
|
27
|
+
// the CPU reads once the KERNAL is banked out, which
|
|
28
|
+
// native/6502/raster.s fills — so it is not a shape block.
|
|
29
|
+
//
|
|
30
|
+
// In bitmap mode (@8bitscript/c64/bitmap) the same bank holds, instead:
|
|
31
|
+
//
|
|
32
|
+
// $E000-$FF3F the 8000-byte bitmap (the second 8K of the bank: $D018
|
|
33
|
+
// bit 3 set), over the screen matrix and the shape blocks.
|
|
34
|
+
// $DC00-$DFE7 the colour matrix, one byte per cell — high nybble the
|
|
35
|
+
// colour of set bits, low nybble of clear bits (hi-res), or
|
|
36
|
+
// the %01 and %10 colours (multicolour) — in the RAM under
|
|
37
|
+
// the I/O area, where the CPU writes only with the area
|
|
38
|
+
// banked out (writeUnderIo in ./index.8bs). It is the second
|
|
39
|
+
// half of the lower-case set, which bitmap mode gives up;
|
|
40
|
+
// the upper-case set at $D000-$D7FF is untouched, so leaving
|
|
41
|
+
// bitmap mode is a register change, not a second ROM copy.
|
|
42
|
+
// $DFF8-$DFFF the sprite pointers, matrix + $3F8 as always.
|
|
43
|
+
// $D800-$DBFF 16 shape blocks (96-111) for sprites over a bitmap, under
|
|
44
|
+
// the I/O area too (the first half of the lower-case set).
|
|
45
|
+
//
|
|
46
|
+
// The colour matrix could not go anywhere the CPU reaches freely: every
|
|
47
|
+
// 1K slot of the bank is either the program's ($C000-$CFFF), the bitmap's
|
|
48
|
+
// ($E000-$FFFF), or under the I/O area.
|
|
49
|
+
//
|
|
50
|
+
// Colour RAM does not move with the bank: it is the 1K of nybbles at $D800
|
|
51
|
+
// in the I/O area, wherever the screen is. So a cell's colour is written
|
|
52
|
+
// through I/O ($01 bit 2 set) while its character is written to RAM — the
|
|
53
|
+
// normal state of the machine, and the state setupVideo() leaves it in.
|
|
54
|
+
//
|
|
55
|
+
// Screenshots of a program using this layout under x64sc (text from the
|
|
56
|
+
// copied set, a sprite from block 144) are how these numbers were checked;
|
|
57
|
+
// packages/c64/AGENTS.md has the table.
|
|
58
|
+
|
|
59
|
+
// The addresses themselves, as this module's own consts: `@address` takes
|
|
60
|
+
// a literal or a const declared beside it, so the arrays over the screen,
|
|
61
|
+
// the colour RAM, the sprite pointers and the shape blocks are declared
|
|
62
|
+
// here, where the numbers are, and `Video` names the same numbers for
|
|
63
|
+
// everything else.
|
|
64
|
+
const SCREEN_ADDRESS: usmallint = 0xE000;
|
|
65
|
+
const COLOR_ADDRESS: usmallint = 0xD800;
|
|
66
|
+
const SPRITE_POINTERS_ADDRESS: usmallint = 0xE3F8;
|
|
67
|
+
const SHAPES_ADDRESS: usmallint = 0xE400;
|
|
68
|
+
const BITMAP_ADDRESS: usmallint = 0xE000;
|
|
69
|
+
|
|
70
|
+
// The screen matrix: 1000 screen codes, cell 0 top-left.
|
|
71
|
+
@address(SCREEN_ADDRESS)
|
|
72
|
+
export let screenRam: array<u8, 1000>;
|
|
73
|
+
// Colour RAM: the low nybble of each byte is that cell's colour.
|
|
74
|
+
@address(COLOR_ADDRESS)
|
|
75
|
+
export let colorRam: array<u8, 1000>;
|
|
76
|
+
// Sprite n's shape block: the VIC reads shape data from bank + 64 * pointer.
|
|
77
|
+
@address(SPRITE_POINTERS_ADDRESS)
|
|
78
|
+
export let spritePointers: array<u8, 8>;
|
|
79
|
+
// The shape blocks, one array: block b's 63 bytes start at 64 * (b - 144).
|
|
80
|
+
@address(SHAPES_ADDRESS)
|
|
81
|
+
export let spriteShapes: array<u8, 7104>;
|
|
82
|
+
// The bitmap, in bitmap mode: 8000 bytes, cell by cell (8 bytes a cell,
|
|
83
|
+
// 40 cells a row — see @8bitscript/c64/bitmap's offset()). The same RAM
|
|
84
|
+
// as the screen matrix and the shape blocks above.
|
|
85
|
+
@address(BITMAP_ADDRESS)
|
|
86
|
+
export let bitmapRam: array<u8, 8000>;
|
|
87
|
+
|
|
88
|
+
export namespace Video {
|
|
89
|
+
const BANK: usmallint = 0xC000; // VIC bank 3, selected through CIA2 port A bits 0-1 (= %00)
|
|
90
|
+
const SCREEN: usmallint = SCREEN_ADDRESS; // screen matrix: one screen code per cell
|
|
91
|
+
const COLOR: usmallint = COLOR_ADDRESS; // colour RAM: one nybble per cell, same cell index, never moves
|
|
92
|
+
const CHARSET: usmallint = 0xD000; // the character ROM's copy in RAM: upper-case set here, lower-case at +$0800
|
|
93
|
+
const SPRITE_POINTERS: usmallint = SPRITE_POINTERS_ADDRESS; // eight bytes: shape block per sprite (address / 64 within the bank)
|
|
94
|
+
const SHAPES: usmallint = SHAPES_ADDRESS; // first sprite shape block
|
|
95
|
+
const SHAPE_BLOCK_FIRST: utinyint = 144; // (SHAPES - BANK) / 64
|
|
96
|
+
const SHAPE_COUNT: utinyint = 111; // blocks 144-254
|
|
97
|
+
const MEMORY_POINTER_UPPERCASE: utinyint = 0x84; // $D018: screen at bank+$2000 (bits 4-7 = 8), charset at bank+$1000 (bits 1-3 = 2)
|
|
98
|
+
const MEMORY_POINTER_LOWERCASE: utinyint = 0x86; // the same screen, the lower-case set at bank+$1800
|
|
99
|
+
const COLUMNS: utinyint = 40;
|
|
100
|
+
const ROWS: utinyint = 25;
|
|
101
|
+
const CELL_COUNT: usmallint = 1000; // COLUMNS * ROWS
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
// Bitmap mode's layout, in the same bank; see the header.
|
|
105
|
+
export namespace Bitmap {
|
|
106
|
+
const ADDRESS: usmallint = BITMAP_ADDRESS; // 8000 bytes, bank + $2000
|
|
107
|
+
const BYTES: usmallint = 8000;
|
|
108
|
+
const WIDTH: usmallint = 320; // pixels, hi-res
|
|
109
|
+
const HEIGHT: utinyint = 200;
|
|
110
|
+
const MATRIX: usmallint = 0xDC00; // the colour matrix, under the I/O area: bank + $1C00
|
|
111
|
+
const SPRITE_POINTERS: usmallint = 0xDFF8; // MATRIX + $3F8
|
|
112
|
+
const SHAPES: usmallint = 0xD800; // 16 shape blocks under the I/O area
|
|
113
|
+
const SHAPE_BLOCK_FIRST: utinyint = 96; // (SHAPES - BANK) / 64
|
|
114
|
+
const SHAPE_COUNT: utinyint = 16; // blocks 96-111
|
|
115
|
+
const MEMORY_POINTER: utinyint = 0x78; // $D018: matrix at bank+$1C00 (bits 4-7 = 7), bitmap at bank+$2000 (bit 3)
|
|
116
|
+
}
|