@8bitscript/mega65 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/package.json +63 -0
- package/src/index.8bs +79 -0
- package/src/input.8bs +168 -0
- package/src/pointer.8bs +40 -0
- package/src/screen.8bs +106 -0
- package/src/text.8bs +175 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 8BitScript contributors
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/package.json
ADDED
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@8bitscript/mega65",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "MEGA65 target support for 8BitScript: the hardware underneath the portable APIs.",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"8bitscript": {
|
|
7
|
+
"entry": "./src/index.8bs",
|
|
8
|
+
"exports": {
|
|
9
|
+
"./screen": "./src/screen.8bs",
|
|
10
|
+
"./text": "./src/text.8bs",
|
|
11
|
+
"./input": "./src/input.8bs",
|
|
12
|
+
"./pointer": "./src/pointer.8bs"
|
|
13
|
+
},
|
|
14
|
+
"hardware": {
|
|
15
|
+
"facts": {
|
|
16
|
+
"video.columns": 80,
|
|
17
|
+
"video.rows": 25,
|
|
18
|
+
"video.cellWidth": 8,
|
|
19
|
+
"video.cellHeight": 8,
|
|
20
|
+
"video.palette": 256,
|
|
21
|
+
"video.cellColors": 2,
|
|
22
|
+
"video.colorPerCell": true,
|
|
23
|
+
"video.glyphs": 256,
|
|
24
|
+
"video.blockWidth": 2,
|
|
25
|
+
"video.blockHeight": 2,
|
|
26
|
+
"video.bitmap": true,
|
|
27
|
+
"video.layers": 1,
|
|
28
|
+
"video.scroll": true,
|
|
29
|
+
"video.sprites": 8,
|
|
30
|
+
"video.spritesPerLine": 8,
|
|
31
|
+
"video.spriteWidth": 64,
|
|
32
|
+
"video.spriteHeight": 255,
|
|
33
|
+
"video.spriteColors": 15,
|
|
34
|
+
"video.frameRate": 60,
|
|
35
|
+
"audio.voices": 16,
|
|
36
|
+
"audio.noise": true,
|
|
37
|
+
"audio.envelope": true,
|
|
38
|
+
"audio.filter": true,
|
|
39
|
+
"audio.pcm": true,
|
|
40
|
+
"audio.volume": true,
|
|
41
|
+
"audio.entropy": true,
|
|
42
|
+
"input.keyboard": true,
|
|
43
|
+
"input.joysticks": 2,
|
|
44
|
+
"input.pads": 0,
|
|
45
|
+
"input.mouse": false,
|
|
46
|
+
"input.paddles": false,
|
|
47
|
+
"storage.save": true,
|
|
48
|
+
"memory.ram": 45055,
|
|
49
|
+
"memory.banked": true,
|
|
50
|
+
"memory.bankedKib": 320,
|
|
51
|
+
"storage.kib": 783
|
|
52
|
+
},
|
|
53
|
+
"options": {},
|
|
54
|
+
"presets": {}
|
|
55
|
+
}
|
|
56
|
+
},
|
|
57
|
+
"files": [
|
|
58
|
+
"src"
|
|
59
|
+
],
|
|
60
|
+
"publishConfig": {
|
|
61
|
+
"access": "public"
|
|
62
|
+
}
|
|
63
|
+
}
|
package/src/index.8bs
ADDED
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
// @8bitscript/mega65 — MEGA65 target support: the hardware underneath.
|
|
2
|
+
//
|
|
3
|
+
// The module entry point named by the "8bitscript".entry field in this
|
|
4
|
+
// package's package.json, resolved and linked for:
|
|
5
|
+
//
|
|
6
|
+
// import { borderColor, backgroundColor, memoryPointer } from "@8bitscript/mega65";
|
|
7
|
+
//
|
|
8
|
+
// The surface is the hardware, one register at a time. What a program
|
|
9
|
+
// usually wants sits one layer up, in this package's own implementations of
|
|
10
|
+
// the portable capability packages — `./src/screen.8bs` behind
|
|
11
|
+
// @8bitscript/screen and `./src/text.8bs` behind @8bitscript/text, named by
|
|
12
|
+
// the "8bitscript".exports map in package.json — which import these
|
|
13
|
+
// registers and are built on nothing else.
|
|
14
|
+
//
|
|
15
|
+
// A program built with mos-mega65-clang loads at $2001 and runs in MEGA65
|
|
16
|
+
// mode: the VIC-IV I/O personality is live, the CPU is at 40.5 MHz, the
|
|
17
|
+
// ROM's screen is 80 columns wide at $0800, and the SDK's start-up leaves
|
|
18
|
+
// interrupts off (`sei`, `$01 = $3E`, `$D030 = $44`) until exit — all
|
|
19
|
+
// measured under xmega65, see AGENTS.md. The registers below are the
|
|
20
|
+
// VIC-II-compatible ones the VIC-IV still answers to at the C64's
|
|
21
|
+
// addresses, which is enough for a border, a background, and a character
|
|
22
|
+
// set; the MEGA65's own registers (SCRNPTR, the palettes, the DMAgic, the
|
|
23
|
+
// four SIDs) are not exported yet, and AGENTS.md is where their rules are.
|
|
24
|
+
|
|
25
|
+
// $D020, the VIC-II(-compatible) border colour register.
|
|
26
|
+
@address(0xD020)
|
|
27
|
+
export let borderColor: volatile<u8>;
|
|
28
|
+
|
|
29
|
+
// $D021, the VIC-II(-compatible) background colour register (background #0).
|
|
30
|
+
@address(0xD021)
|
|
31
|
+
export let backgroundColor: volatile<u8>;
|
|
32
|
+
|
|
33
|
+
// $D018, the VIC-II-compatible memory pointer, laid out as the C64's: bits
|
|
34
|
+
// 4-7 the screen base in 1K units, bits 1-3 the character set base in 2K
|
|
35
|
+
// units. mega65.h names the default screen character matrix directly:
|
|
36
|
+
// DEFAULT_SCREEN at $0800 — not the C64's $0400 — so the screen half is
|
|
37
|
+
// $2x, and $24 selects the upper-case/graphics set at $1000 (the same 2K
|
|
38
|
+
// the C64 has there) where $26 would be the lower-case set at $1800. The
|
|
39
|
+
// MEGA65 re-derives its own wider VIC-IV pointers from a legacy $D018
|
|
40
|
+
// write, so this is enough; verified on screen under xmega65, not inferred.
|
|
41
|
+
@address(0xD018)
|
|
42
|
+
export let memoryPointer: volatile<u8>;
|
|
43
|
+
|
|
44
|
+
// $D030, the VIC-III control register A. The SDK's start-up sets it to $44
|
|
45
|
+
// (PAL-RAM on, CROM9 on, no C65 ROM mapped, CRAM2K clear). Bit 0, CRAM2K,
|
|
46
|
+
// maps the second kilobyte of colour RAM at $DC00-$DFFF in place of the
|
|
47
|
+
// two CIAs — which the 80-column screen's cells 1024-1999 need, and which
|
|
48
|
+
// text.8bs sets before every run of text (the frame runtime polls $D012,
|
|
49
|
+
// not a CIA, so nothing here misses them). Writing anything but bit 0
|
|
50
|
+
// re-maps ROM over the program: keep the other bits as the SDK left them.
|
|
51
|
+
@address(0xD030)
|
|
52
|
+
export let vicIIIControlA: volatile<u8>;
|
|
53
|
+
|
|
54
|
+
export namespace VicIIIControl {
|
|
55
|
+
const SDK_STARTUP: utinyint = 0x44; // what .init.010 stores
|
|
56
|
+
const COLOR_RAM_2K: utinyint = 0x45; // the same, plus CRAM2K
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
// ---- CIA1 ($DC00): the keyboard matrix and the two control ports ------------
|
|
60
|
+
//
|
|
61
|
+
// A MEGA65 is a C64 here: the same chip at the same address, the same
|
|
62
|
+
// eight columns by eight rows, the same key in the same place, and the
|
|
63
|
+
// same two control ports on the same bits. So @8bitscript/c64/keys's codes
|
|
64
|
+
// are the right codes on this machine, and `@8bitscript/mega65/input`
|
|
65
|
+
// reads them exactly as the C64's layer does.
|
|
66
|
+
//
|
|
67
|
+
// What this machine adds and that layer does not yet read: the MEGA65's
|
|
68
|
+
// own extended keyboard — the full-travel keys a C64 never had, including
|
|
69
|
+
// **ALT**, CAPS LOCK and the four dedicated cursor keys — which the
|
|
70
|
+
// hardware presents through its own registers rather than through this
|
|
71
|
+
// matrix. That is the next thing to wire up here.
|
|
72
|
+
@address(0xDC00)
|
|
73
|
+
export let cia1PortA: volatile<u8>;
|
|
74
|
+
@address(0xDC01)
|
|
75
|
+
export let cia1PortB: volatile<u8>;
|
|
76
|
+
@address(0xDC02)
|
|
77
|
+
export let cia1DirectionA: volatile<u8>;
|
|
78
|
+
@address(0xDC03)
|
|
79
|
+
export let cia1DirectionB: volatile<u8>;
|
package/src/input.8bs
ADDED
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
// @8bitscript/mega65/input — the MEGA65 behind @8bitscript/input.
|
|
2
|
+
//
|
|
3
|
+
// Named by "8bitscript".exports["./input"] in this package's package.json,
|
|
4
|
+
// and by "8bitscript".entry.mega65 in @8bitscript/input's, so a portable
|
|
5
|
+
// program writes
|
|
6
|
+
//
|
|
7
|
+
// import { input } from "@8bitscript/input";
|
|
8
|
+
//
|
|
9
|
+
// and gets this file on a MEGA65.
|
|
10
|
+
//
|
|
11
|
+
// ---- a C64's matrix on a machine that is not a C64 -----------------------
|
|
12
|
+
//
|
|
13
|
+
// For input, this machine is a C64: CIA1 at $DC00, eight columns by eight
|
|
14
|
+
// rows, the same key in the same place, the two control ports on the same
|
|
15
|
+
// bits. So the codes below are @8bitscript/c64/keys's codes, and the scan
|
|
16
|
+
// is the C64's — write a byte with one 0 bit to port A to select a column,
|
|
17
|
+
// read port B for that column's eight keys.
|
|
18
|
+
//
|
|
19
|
+
// - **left / right / up / down** — the C64-shaped cursor keys, with
|
|
20
|
+
// SHIFT reversing each, or'd with a joystick in port 2.
|
|
21
|
+
// - **confirm** — RETURN, or fire. **cancel** — RUN/STOP.
|
|
22
|
+
//
|
|
23
|
+
// **What this leaves on the table is most of the keyboard.** A MEGA65 has
|
|
24
|
+
// full-travel keys a C64 never had — four dedicated cursor keys, **ALT**,
|
|
25
|
+
// CAPS LOCK, NO SCROLL, HELP — presented through the machine's own
|
|
26
|
+
// registers rather than through this matrix. Reading them is the next
|
|
27
|
+
// thing to do here, and it would make this the machine where a menu takes
|
|
28
|
+
// ALT+letter. Until then a MEGA65 navigates exactly like a C64, which is
|
|
29
|
+
// correct but is less than the hardware offers, and that is worth knowing
|
|
30
|
+
// rather than discovering.
|
|
31
|
+
//
|
|
32
|
+
// **No mouse.** This package's hardware catalog has no options at all yet,
|
|
33
|
+
// so `input.mouse` is false and there is nothing to detect. A 1351 on a
|
|
34
|
+
// MEGA65 would be the C64's driver against these same registers.
|
|
35
|
+
import { cia1PortA, cia1PortB, cia1DirectionA, cia1DirectionB } from "./index.8bs";
|
|
36
|
+
|
|
37
|
+
// The C64 matrix's codes, for the keys this file maps.
|
|
38
|
+
namespace Key {
|
|
39
|
+
const RETURN: utinyint = 1;
|
|
40
|
+
const CURSOR_RIGHT: utinyint = 2; // shifted: cursor left
|
|
41
|
+
const CURSOR_DOWN: utinyint = 7; // shifted: cursor up
|
|
42
|
+
const SHIFT_LEFT: utinyint = 15;
|
|
43
|
+
const SHIFT_RIGHT: utinyint = 52;
|
|
44
|
+
const STOP: utinyint = 63; // RUN/STOP
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
const COLUMN_SELECT: array<utinyint, 8> = [254, 253, 251, 247, 239, 223, 191, 127];
|
|
48
|
+
const ROW_BIT: array<utinyint, 8> = [1, 2, 4, 8, 16, 32, 64, 128];
|
|
49
|
+
|
|
50
|
+
let matrix: array<utinyint, 8>;
|
|
51
|
+
|
|
52
|
+
namespace Edge {
|
|
53
|
+
const LEFT: utinyint = 1;
|
|
54
|
+
const RIGHT: utinyint = 2;
|
|
55
|
+
const UP: utinyint = 4;
|
|
56
|
+
const DOWN: utinyint = 8;
|
|
57
|
+
const CONFIRM: utinyint = 16;
|
|
58
|
+
const CANCEL: utinyint = 32;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
let held: utinyint = 0;
|
|
62
|
+
let before: utinyint = 0;
|
|
63
|
+
let began: utinyint = 0;
|
|
64
|
+
|
|
65
|
+
// Is this key down in the last scan?
|
|
66
|
+
function keyDown(key: utinyint): bool {
|
|
67
|
+
return (matrix[key >> 3] & ROW_BIT[key & 7]) != 0;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
export namespace input {
|
|
71
|
+
|
|
72
|
+
// Nothing to set up: no pointer to give a ceiling to.
|
|
73
|
+
function begin(): void {
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
function poll(): void {
|
|
77
|
+
cia1DirectionA = 0xFF;
|
|
78
|
+
cia1DirectionB = 0x00;
|
|
79
|
+
for (let column: utinyint = 0; column < 8; column++) {
|
|
80
|
+
cia1PortA = COLUMN_SELECT[column];
|
|
81
|
+
matrix[column] = cia1PortB ^ 0xFF;
|
|
82
|
+
}
|
|
83
|
+
cia1PortA = 0xFF;
|
|
84
|
+
// Port A back at $FF is what makes port B read as joystick 1 and
|
|
85
|
+
// port A as joystick 2, exactly as on a C64.
|
|
86
|
+
let stick: utinyint = (cia1PortA ^ 0xFF) & 0x1F;
|
|
87
|
+
|
|
88
|
+
before = held;
|
|
89
|
+
held = 0;
|
|
90
|
+
|
|
91
|
+
let shifted: bool = keyDown(Key.SHIFT_LEFT) || keyDown(Key.SHIFT_RIGHT);
|
|
92
|
+
if (keyDown(Key.CURSOR_RIGHT)) {
|
|
93
|
+
if (shifted) {
|
|
94
|
+
held = held | Edge.LEFT;
|
|
95
|
+
} else {
|
|
96
|
+
held = held | Edge.RIGHT;
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
if (keyDown(Key.CURSOR_DOWN)) {
|
|
100
|
+
if (shifted) {
|
|
101
|
+
held = held | Edge.UP;
|
|
102
|
+
} else {
|
|
103
|
+
held = held | Edge.DOWN;
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
if (keyDown(Key.RETURN)) {
|
|
107
|
+
held = held | Edge.CONFIRM;
|
|
108
|
+
}
|
|
109
|
+
if (keyDown(Key.STOP)) {
|
|
110
|
+
held = held | Edge.CANCEL;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
// The stick in port 2: up 1, down 2, left 4, right 8, fire 16.
|
|
114
|
+
if ((stick & 4) != 0) {
|
|
115
|
+
held = held | Edge.LEFT;
|
|
116
|
+
}
|
|
117
|
+
if ((stick & 8) != 0) {
|
|
118
|
+
held = held | Edge.RIGHT;
|
|
119
|
+
}
|
|
120
|
+
if ((stick & 1) != 0) {
|
|
121
|
+
held = held | Edge.UP;
|
|
122
|
+
}
|
|
123
|
+
if ((stick & 2) != 0) {
|
|
124
|
+
held = held | Edge.DOWN;
|
|
125
|
+
}
|
|
126
|
+
if ((stick & 16) != 0) {
|
|
127
|
+
held = held | Edge.CONFIRM;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
began = held & (before ^ 0xFF);
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
function left(): bool {
|
|
134
|
+
return (began & Edge.LEFT) != 0;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
function right(): bool {
|
|
138
|
+
return (began & Edge.RIGHT) != 0;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
function up(): bool {
|
|
142
|
+
return (began & Edge.UP) != 0;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
function down(): bool {
|
|
146
|
+
return (began & Edge.DOWN) != 0;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
function confirm(): bool {
|
|
150
|
+
return (began & Edge.CONFIRM) != 0;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
function cancel(): bool {
|
|
154
|
+
return (began & Edge.CANCEL) != 0;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
function pointer(): bool {
|
|
158
|
+
return false;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
function pointerCell(): usmallint {
|
|
162
|
+
return 0;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
function pointerButton(): bool {
|
|
166
|
+
return false;
|
|
167
|
+
}
|
|
168
|
+
}
|
package/src/pointer.8bs
ADDED
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
// @8bitscript/mega65/pointer — the MEGA65 behind @8bitscript/pointer.
|
|
2
|
+
//
|
|
3
|
+
// Every call here does nothing and `DRAWS` is false, and as with the C128
|
|
4
|
+
// this is a missing driver rather than a missing capability: the VIC-IV is
|
|
5
|
+
// a superset of the VIC-II, so the eight sprites are there (and more
|
|
6
|
+
// besides), and the machine has control ports a 1351 fits.
|
|
7
|
+
//
|
|
8
|
+
// Two things are missing, in this order. **The input half:**
|
|
9
|
+
// @8bitscript/mega65/input reads the keyboard and the C64's joystick
|
|
10
|
+
// arrangement and reports no pointer, because packages/mega65's catalog
|
|
11
|
+
// has no control-port options at all yet — no value to set
|
|
12
|
+
// `#fact(input.mouse)` true, so nothing to fit and no fact to compile a
|
|
13
|
+
// pointer in from. **Then the drawing half:** this package has no sprites
|
|
14
|
+
// layer, so there is nothing for a file like @8bitscript/c64/pointer to
|
|
15
|
+
// sit on.
|
|
16
|
+
//
|
|
17
|
+
// Worth knowing before either is written: this machine starts up with
|
|
18
|
+
// interrupts off and at 40.5 MHz (see packages/mega65/AGENTS.md), so it
|
|
19
|
+
// has cycles to spare for a pointer — the constraint here is entirely that
|
|
20
|
+
// nobody has written the two layers, not the hardware.
|
|
21
|
+
//
|
|
22
|
+
// It costs the MEGA65 nothing meanwhile: `DRAWS` is a const and every
|
|
23
|
+
// function is empty, so a program's `if (pointer.DRAWS)` folds away and
|
|
24
|
+
// LLVM deletes the calls.
|
|
25
|
+
export namespace pointer {
|
|
26
|
+
|
|
27
|
+
const DRAWS: bool = false;
|
|
28
|
+
|
|
29
|
+
function begin(): void {
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function setColor(color: utinyint): void {
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function update(): void {
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function hide(): void {
|
|
39
|
+
}
|
|
40
|
+
}
|
package/src/screen.8bs
ADDED
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
// @8bitscript/mega65/screen — the MEGA65's implementation of @8bitscript/screen.
|
|
2
|
+
//
|
|
3
|
+
// Named by "8bitscript".exports["./screen"] in this package's package.json,
|
|
4
|
+
// and what
|
|
5
|
+
//
|
|
6
|
+
// import { screen, BorderColor, BackgroundColor } from "@8bitscript/screen";
|
|
7
|
+
//
|
|
8
|
+
// resolves to when the build is for the MEGA65: @8bitscript/screen's entry
|
|
9
|
+
// is keyed by machine and delegates here. Built on the registers
|
|
10
|
+
// @8bitscript/mega65 itself exports, one layer up from them. Every machine's
|
|
11
|
+
// screen.8bs exports this same surface, which is what lets a program import
|
|
12
|
+
// it from @8bitscript/screen and never name the hardware.
|
|
13
|
+
import { borderColor, backgroundColor } from "./index.8bs";
|
|
14
|
+
|
|
15
|
+
// ---- the screen: border and background --------------------------------------
|
|
16
|
+
//
|
|
17
|
+
// Border and background have their own registers at the C64's addresses;
|
|
18
|
+
// the VIC-IV's are 8 bits wide (256 palette entries), and the shared eight
|
|
19
|
+
// names stay on 0-15 so a program's colour means the same thing everywhere.
|
|
20
|
+
// The screen a build runs on is 80x25 at $0800 (see text.8bs), so a blank
|
|
21
|
+
// clears 2000 cells.
|
|
22
|
+
export namespace screen {
|
|
23
|
+
function setColors(border: u8, background: u8): void {
|
|
24
|
+
borderColor = border & 15;
|
|
25
|
+
backgroundColor = background & 15;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
// `blank(border, background)`: every cell blank and both colours set —
|
|
29
|
+
// black when left off, or `BorderColor.KEEP` / `BackgroundColor.KEEP`
|
|
30
|
+
// to leave one as it is. `setBorder` and `setBackground` are one
|
|
31
|
+
// colour each, `setColors` the pair; `text.setColor` owns what the
|
|
32
|
+
// next print looks like.
|
|
33
|
+
function blank(border: utinyint = BorderColor.BLACK, background: utinyint = BackgroundColor.BLACK): void {
|
|
34
|
+
if (border != BorderColor.KEEP) {
|
|
35
|
+
screen.setBorder(border);
|
|
36
|
+
}
|
|
37
|
+
if (background != BackgroundColor.KEEP) {
|
|
38
|
+
screen.setBackground(background);
|
|
39
|
+
}
|
|
40
|
+
for (let cell: usmallint = 0; cell < 2000; cell++) {
|
|
41
|
+
memory.write(0x0800 + cell, 32); // 32: the space screen code
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
function setBackground(background: u8): void {
|
|
46
|
+
backgroundColor = background & 15;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
function setBorder(border: u8): void {
|
|
50
|
+
borderColor = border & 15;
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
// ---- colour names ----------------------------------------------------------
|
|
55
|
+
//
|
|
56
|
+
// The same eight names every machine's screen.8bs exports — Black, White,
|
|
57
|
+
// Red, Cyan, Purple, Green, Blue, Yellow — so a program can write
|
|
58
|
+
// `screen.setColors(BorderColor.BLUE, BackgroundColor.BLACK)` through
|
|
59
|
+
// @8bitscript/screen and get blue on every target; the values are this
|
|
60
|
+
// machine's own. In the VIC-II-compatible view both registers take the
|
|
61
|
+
// C64's sixteen colours in the C64's numbering, so both namespaces carry
|
|
62
|
+
// all sixteen.
|
|
63
|
+
|
|
64
|
+
export namespace BorderColor {
|
|
65
|
+
const BLACK: utinyint = 0;
|
|
66
|
+
const WHITE: utinyint = 1;
|
|
67
|
+
const RED: utinyint = 2;
|
|
68
|
+
const CYAN: utinyint = 3;
|
|
69
|
+
const PURPLE: utinyint = 4;
|
|
70
|
+
const GREEN: utinyint = 5;
|
|
71
|
+
const BLUE: utinyint = 6;
|
|
72
|
+
const YELLOW: utinyint = 7;
|
|
73
|
+
const ORANGE: utinyint = 8;
|
|
74
|
+
const BROWN: utinyint = 9;
|
|
75
|
+
const LIGHT_RED: utinyint = 10;
|
|
76
|
+
const DARK_GREY: utinyint = 11;
|
|
77
|
+
const GREY: utinyint = 12;
|
|
78
|
+
const LIGHT_GREEN: utinyint = 13;
|
|
79
|
+
const LIGHT_BLUE: utinyint = 14;
|
|
80
|
+
const LIGHT_GREY: utinyint = 15;
|
|
81
|
+
// Not a colour: `blank(KEEP, ...)` leaves this one as it is. 255 is
|
|
82
|
+
// a value no register here takes.
|
|
83
|
+
const KEEP: utinyint = 255;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
export namespace BackgroundColor {
|
|
87
|
+
const BLACK: utinyint = 0;
|
|
88
|
+
const WHITE: utinyint = 1;
|
|
89
|
+
const RED: utinyint = 2;
|
|
90
|
+
const CYAN: utinyint = 3;
|
|
91
|
+
const PURPLE: utinyint = 4;
|
|
92
|
+
const GREEN: utinyint = 5;
|
|
93
|
+
const BLUE: utinyint = 6;
|
|
94
|
+
const YELLOW: utinyint = 7;
|
|
95
|
+
const ORANGE: utinyint = 8;
|
|
96
|
+
const BROWN: utinyint = 9;
|
|
97
|
+
const LIGHT_RED: utinyint = 10;
|
|
98
|
+
const DARK_GREY: utinyint = 11;
|
|
99
|
+
const GREY: utinyint = 12;
|
|
100
|
+
const LIGHT_GREEN: utinyint = 13;
|
|
101
|
+
const LIGHT_BLUE: utinyint = 14;
|
|
102
|
+
const LIGHT_GREY: utinyint = 15;
|
|
103
|
+
// Not a colour: `blank(KEEP, ...)` leaves this one as it is. 255 is
|
|
104
|
+
// a value no register here takes.
|
|
105
|
+
const KEEP: utinyint = 255;
|
|
106
|
+
}
|
package/src/text.8bs
ADDED
|
@@ -0,0 +1,175 @@
|
|
|
1
|
+
// @8bitscript/mega65/text — the MEGA65's implementation of @8bitscript/text.
|
|
2
|
+
//
|
|
3
|
+
// Named by "8bitscript".exports["./text"] in this package's package.json,
|
|
4
|
+
// and what
|
|
5
|
+
//
|
|
6
|
+
// import { text } from "@8bitscript/text";
|
|
7
|
+
//
|
|
8
|
+
// resolves to when the build is for the MEGA65. Built on the registers
|
|
9
|
+
// @8bitscript/mega65 exports. Every machine's text.8bs exports this same
|
|
10
|
+
// namespace — the same names, the same shapes, ASCII codes, and a cell 0
|
|
11
|
+
// at the top-left corner inside the border — so a program that imports it
|
|
12
|
+
// from @8bitscript/text draws the same thing on every target.
|
|
13
|
+
import { memoryPointer, vicIIIControlA, VicIIIControl } from "./index.8bs";
|
|
14
|
+
|
|
15
|
+
// The screen is addressed by flat cell index — a position is
|
|
16
|
+
// `y * text.COLUMNS + x` — and these are the plainest things a text API
|
|
17
|
+
// can be built from: poke one cell's character code, poke one cell's colour, and how many
|
|
18
|
+
// cells the whole screen has. Whether a caller uses these to clear the
|
|
19
|
+
// screen, lay out a label, or nothing at all is entirely up to the caller —
|
|
20
|
+
// this package draws nothing on its own. mega65.h names the default screen
|
|
21
|
+
// character matrix directly: DEFAULT_SCREEN at $0800 — not the C64's $0400
|
|
22
|
+
// — and in the MEGA65 mode a build runs in, that screen is 80x25, 2000
|
|
23
|
+
// cells (LINESTEP 80, CHRCOUNT 80, H640 set: measured under xmega65, see
|
|
24
|
+
// AGENTS.md; an earlier version of this file drew for 40 columns and
|
|
25
|
+
// wrapped every second row onto the right half of the one above). Colour
|
|
26
|
+
// RAM is at $D800 for cells 0-1023, and for cells 1024-1999 at
|
|
27
|
+
// $DC00-$DFCF only while $D030's CRAM2K bit is set — otherwise those
|
|
28
|
+
// addresses are the two CIAs — so prepare() sets that bit before a run.
|
|
29
|
+
//
|
|
30
|
+
// ---- character codes: ASCII in, screen codes out ----------------------------
|
|
31
|
+
//
|
|
32
|
+
// `text.putChar` takes ASCII on every machine — space (32), '0'-'9'
|
|
33
|
+
// (48-57), 'A'-'Z' (65-90) and the punctuation `! , - . : ?` — upper case
|
|
34
|
+
// only: that is the portable set, the characters every target's character
|
|
35
|
+
// set has (the NES ships its own font, and that is what it has). The
|
|
36
|
+
// hardware wants screen codes: in every Commodore character ROM, codes
|
|
37
|
+
// 32-63 are those same ASCII values (space, digits, that punctuation), and
|
|
38
|
+
// 'A'-'Z' are 1-26, so asciiToScreenCode() moves 64-95 down by 64 and
|
|
39
|
+
// leaves 32-63 alone. Whether 1-26 then LOOK like 'A'-'Z' or 'a'-'z' is
|
|
40
|
+
// the character set's choice — the ROM's boot state, whatever ran before,
|
|
41
|
+
// a user's SHIFT+C= — so this package owns the choice rather than
|
|
42
|
+
// inheriting one (LLVM-MOS's libc used to flip the machine to lower-case
|
|
43
|
+
// before main() with a hidden CHR$(14); packages/backend-6502's
|
|
44
|
+
// commodoreCharsetGuard() keeps that out of the link). putChar() selects
|
|
45
|
+
// the upper-case set, every
|
|
46
|
+
// call, through the memory pointer (see @8bitscript/mega65's
|
|
47
|
+
// `memoryPointer` for the $24 it takes here): one register write, and
|
|
48
|
+
// "TICK" reads as TICK here the way it does on the NES, the Atari, and the
|
|
49
|
+
// X16, none of which have a lower-case set to fall into.
|
|
50
|
+
namespace CharacterSet {
|
|
51
|
+
const UPPERCASE_SCREEN_AT_0800: utinyint = 0x24;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
function asciiToScreenCode(code: utinyint): utinyint {
|
|
55
|
+
if (code >= 64 && code < 96) {
|
|
56
|
+
return code - 64;
|
|
57
|
+
}
|
|
58
|
+
return code;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
// ---- print: strings and number fields ------------------------------------
|
|
62
|
+
//
|
|
63
|
+
// `text.print(cell, s)` writes a string's characters into consecutive
|
|
64
|
+
// cells from `cell`, and `text.printNumber(cell, value, width)` writes
|
|
65
|
+
// `value` as exactly `width` decimal digits, zero-padded and right-aligned,
|
|
66
|
+
// so a field on a HUD never shifts columns. Both draw in the current colour —
|
|
67
|
+
// white until `text.setColor(TextColor.CYAN)` changes it, and that one
|
|
68
|
+
// call then colours everything printed after it, on the machines that have
|
|
69
|
+
// per-cell colour. They are also the two
|
|
70
|
+
// functions the compiler's template layout targets: `text.print(0,
|
|
71
|
+
// \`TICK ${ticks:1}\`)` is laid out at compile time into these same calls
|
|
72
|
+
// (see packages/compiler/src/ir).
|
|
73
|
+
//
|
|
74
|
+
// Under them: `place()` puts one character at one cell in the current
|
|
75
|
+
// colour, and is all a run of text costs per character, with `prepare()` selecting the
|
|
76
|
+
// upper-case set once for the run.
|
|
77
|
+
// `text.putChar` and `text.putColor` stay the one-cell pokes a caller can
|
|
78
|
+
// build anything from.
|
|
79
|
+
let currentColor: utinyint = 1; // white, until text.setColor() says otherwise
|
|
80
|
+
let currentReverse: bool = false; // until text.setReverse() says otherwise
|
|
81
|
+
|
|
82
|
+
// Reverse video is bit 7 of the screen code, as on the C64. Colour RAM's
|
|
83
|
+
// upper nibble is also an attribute field (blink, reverse) — keep it
|
|
84
|
+
// clear, and invert by the ROM copy instead.
|
|
85
|
+
function toScreen(code: utinyint): utinyint {
|
|
86
|
+
let screen: utinyint = asciiToScreenCode(code);
|
|
87
|
+
if (currentReverse) {
|
|
88
|
+
screen = screen + 128;
|
|
89
|
+
}
|
|
90
|
+
return screen;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
// What a run of text needs once, before its first character.
|
|
94
|
+
function prepare(): void {
|
|
95
|
+
memoryPointer = CharacterSet.UPPERCASE_SCREEN_AT_0800;
|
|
96
|
+
vicIIIControlA = VicIIIControl.COLOR_RAM_2K;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
function place(cell: usmallint, code: utinyint): void {
|
|
100
|
+
memory.write(0x0800 + cell, toScreen(code));
|
|
101
|
+
memory.write(0xD800 + cell, currentColor & 15); // the upper nibble is attributes (blink, reverse) — keep it clear
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
// ---- digits -------------------------------------------------------------
|
|
105
|
+
//
|
|
106
|
+
// A number is written one place at a time, high to low, by subtracting the
|
|
107
|
+
// place value until it no longer fits: the 6502 has no divide instruction,
|
|
108
|
+
// and `value / 10` would link a 250-byte routine to do it. Places above the
|
|
109
|
+
// field are still taken off, so a field narrower than its number shows the
|
|
110
|
+
// low digits; places the number does not reach print as zeros.
|
|
111
|
+
const DIGIT_PLACES: array<usmallint, 5> = [10000, 1000, 100, 10, 1];
|
|
112
|
+
|
|
113
|
+
export namespace text {
|
|
114
|
+
const CELL_COUNT: usmallint = 2000; // 80 columns x 25 rows
|
|
115
|
+
const COLUMNS: utinyint = 80; // cells per row, so cell = y * text.COLUMNS + x
|
|
116
|
+
|
|
117
|
+
function putChar(cell: usmallint, code: utinyint): void {
|
|
118
|
+
prepare();
|
|
119
|
+
memory.write(0x0800 + cell, toScreen(code));
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
function putColor(cell: usmallint, color: utinyint): void {
|
|
123
|
+
vicIIIControlA = VicIIIControl.COLOR_RAM_2K; // cells past 1023 live behind CRAM2K
|
|
124
|
+
memory.write(0xD800 + cell, color & 15);
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
function setColor(color: utinyint): void {
|
|
128
|
+
currentColor = color;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
function setReverse(on: bool): void {
|
|
132
|
+
currentReverse = on;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
function print(cell: usmallint, s: string): void {
|
|
136
|
+
prepare();
|
|
137
|
+
for (let i: utinyint = 0; i < s.length; i++) {
|
|
138
|
+
place(cell + i, s[i]);
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
function printNumber(cell: usmallint, value: usmallint, width: utinyint): void {
|
|
143
|
+
prepare();
|
|
144
|
+
let k: utinyint = width;
|
|
145
|
+
if (k < DIGIT_PLACES.length) {
|
|
146
|
+
k = DIGIT_PLACES.length;
|
|
147
|
+
}
|
|
148
|
+
while (k > 0) {
|
|
149
|
+
let digit: utinyint = 48; // '0'
|
|
150
|
+
if (k <= DIGIT_PLACES.length) {
|
|
151
|
+
while (value >= DIGIT_PLACES[DIGIT_PLACES.length - k]) {
|
|
152
|
+
value = value - DIGIT_PLACES[DIGIT_PLACES.length - k];
|
|
153
|
+
digit++;
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
if (k <= width) {
|
|
157
|
+
place(cell + width - k, digit);
|
|
158
|
+
}
|
|
159
|
+
k--;
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
// The colours `text.setColor()` takes: the eight names every machine
|
|
165
|
+
// shares, with this machine's colour-RAM values.
|
|
166
|
+
export namespace TextColor {
|
|
167
|
+
const BLACK: utinyint = 0;
|
|
168
|
+
const WHITE: utinyint = 1;
|
|
169
|
+
const RED: utinyint = 2;
|
|
170
|
+
const CYAN: utinyint = 3;
|
|
171
|
+
const PURPLE: utinyint = 4;
|
|
172
|
+
const GREEN: utinyint = 5;
|
|
173
|
+
const BLUE: utinyint = 6;
|
|
174
|
+
const YELLOW: utinyint = 7;
|
|
175
|
+
}
|