@8bitscript/atari8 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 +658 -0
- package/src/banks.8bs +124 -0
- package/src/console.8bs +85 -0
- package/src/index.8bs +150 -0
- package/src/input.8bs +178 -0
- package/src/joystick.8bs +147 -0
- package/src/keyboard.8bs +157 -0
- package/src/keys.8bs +116 -0
- package/src/pointer.8bs +39 -0
- package/src/pokey.8bs +225 -0
- package/src/random.8bs +59 -0
- package/src/screen.8bs +129 -0
- package/src/text.8bs +159 -0
package/src/banks.8bs
ADDED
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
// @8bitscript/atari8/banks — the extra RAM behind PORTB, if this machine
|
|
2
|
+
// has any.
|
|
3
|
+
//
|
|
4
|
+
// Named by "8bitscript".exports["./banks"] in this package's package.json:
|
|
5
|
+
//
|
|
6
|
+
// import { banks } from "@8bitscript/atari8/banks";
|
|
7
|
+
//
|
|
8
|
+
// let kib: usmallint = banks.kib(); // 0 on an 800XL, 64 on a 130XE
|
|
9
|
+
//
|
|
10
|
+
// A run-time hardware probe, like @8bitscript/c64/reu. A 130XE is an
|
|
11
|
+
// 800XL with 64 KiB more RAM, reached four 16 KiB banks at a time through
|
|
12
|
+
// the window at `$4000`–`$7FFF`; the two machines are otherwise the same,
|
|
13
|
+
// one binary runs on both, and this is how it finds out which it is on.
|
|
14
|
+
// The build says which the emulator is fitted with (the catalog's `model`
|
|
15
|
+
// option; `Memory.BANKED_KIB` from @8bitscript/system). A program that
|
|
16
|
+
// never imports this file carries none of it.
|
|
17
|
+
//
|
|
18
|
+
// PORTB (`$D301`) is the PIA's second port, and on the XL/XE it is not a
|
|
19
|
+
// joystick port at all but the memory-control register: bit 0 the OS ROM,
|
|
20
|
+
// bit 1 BASIC, bits 2–3 which extended bank, bit 4 clear to let the *CPU*
|
|
21
|
+
// see it at `$4000`, bit 5 clear to let *ANTIC* see it, bit 7 the
|
|
22
|
+
// self-test ROM. Read from atari800's `MEMORY_HandlePORTB`, where the
|
|
23
|
+
// 128 KiB machine's bank is `((byte & 0x0c) >> 2) + 1` and a machine with
|
|
24
|
+
// no extended RAM runs the same code with no effect — which is exactly
|
|
25
|
+
// what makes the probe work: on an 800XL the window never stops being
|
|
26
|
+
// ordinary RAM.
|
|
27
|
+
//
|
|
28
|
+
// So the question is whether writing through the window with a bank
|
|
29
|
+
// selected leaves base RAM alone. A marker goes into base RAM at `$4000`;
|
|
30
|
+
// then each bank is selected in turn and given a marker of its own; then
|
|
31
|
+
// base RAM is looked at again. If it still holds its marker, the writes
|
|
32
|
+
// went somewhere else and the machine has extended RAM; if it holds the
|
|
33
|
+
// last bank's marker, there was only ever one RAM there. Each bank is
|
|
34
|
+
// then read back to be sure it kept its own marker and is not a mirror of
|
|
35
|
+
// another.
|
|
36
|
+
//
|
|
37
|
+
// Only bits 2, 3 and 4 are ever touched, and only for the length of the
|
|
38
|
+
// probe: bit 5 is left as it was so ANTIC keeps drawing the screen from
|
|
39
|
+
// base RAM throughout, bit 0 so the OS ROM stays in (its vectors are live
|
|
40
|
+
// — the OS's vertical-blank NMI runs whatever this code does, and `sei`
|
|
41
|
+
// does not stop it), and bits 1 and 7 so BASIC and the self-test stay
|
|
42
|
+
// where they were. PORTB is put back exactly as found.
|
|
43
|
+
//
|
|
44
|
+
// **This probe writes to `$4000`–`$4001`**, which is inside the region
|
|
45
|
+
// the linker gives the program (`$2000`–`$BFFF`). It is safe only while
|
|
46
|
+
// the program's own code and data end below `$4000` and its stack is
|
|
47
|
+
// above `$7FFF` — true of anything this toolchain builds today (the
|
|
48
|
+
// borders example ends at `$2332`, and the soft stack starts from MEMTOP,
|
|
49
|
+
// around `$9C00`), and worth checking with `llvm-nm` for a program big
|
|
50
|
+
// enough to reach `$4000`. See ../AGENTS.md.
|
|
51
|
+
|
|
52
|
+
@address(0xD301) let portB: volatile<u8>;
|
|
53
|
+
|
|
54
|
+
namespace Xe {
|
|
55
|
+
const KEEP: utinyint = 0xE3; // bits 0, 1, 5, 6, 7 — everything but the bank and CPU-access bits
|
|
56
|
+
const CPU_BASE: utinyint = 0x10; // bit 4 set: the CPU sees base RAM again
|
|
57
|
+
const WINDOW: usmallint = 0x4000; // first byte of the 16 KiB window
|
|
58
|
+
const BASE_MARK: utinyint = 0x5A; // what base RAM is given to hold
|
|
59
|
+
const BANK_MARK: utinyint = 0xA0; // plus the bank number, what each bank is given
|
|
60
|
+
const BANKS: utinyint = 4; // banks the 130XE's two bits can name
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
export namespace banks {
|
|
64
|
+
// Point the window at extended bank `bank` for the CPU only, keeping
|
|
65
|
+
// every other PORTB bit as `saved` had it.
|
|
66
|
+
function select(bank: utinyint, saved: utinyint): void {
|
|
67
|
+
portB = (saved & Xe.KEEP) | (bank * 4);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
// Put the window back on base RAM.
|
|
71
|
+
function base(saved: utinyint): void {
|
|
72
|
+
portB = saved | Xe.CPU_BASE;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
// Give every bank a marker of its own.
|
|
76
|
+
function markAll(saved: utinyint): void {
|
|
77
|
+
let bank: utinyint = 0;
|
|
78
|
+
while (bank != Xe.BANKS) {
|
|
79
|
+
banks.select(bank, saved);
|
|
80
|
+
memory.write(Xe.WINDOW, Xe.BANK_MARK + bank);
|
|
81
|
+
bank = bank + 1;
|
|
82
|
+
}
|
|
83
|
+
banks.base(saved);
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
// How many banks kept their own marker — 0 if the first one did not,
|
|
87
|
+
// which is what a machine with no extended RAM looks like once base
|
|
88
|
+
// RAM has been overwritten.
|
|
89
|
+
function kept(saved: utinyint): utinyint {
|
|
90
|
+
let bank: utinyint = 0;
|
|
91
|
+
while (bank != Xe.BANKS) {
|
|
92
|
+
banks.select(bank, saved);
|
|
93
|
+
let back: utinyint = memory.read(Xe.WINDOW);
|
|
94
|
+
banks.base(saved);
|
|
95
|
+
if (back != (Xe.BANK_MARK + bank)) {
|
|
96
|
+
return bank;
|
|
97
|
+
}
|
|
98
|
+
bank = bank + 1;
|
|
99
|
+
}
|
|
100
|
+
return Xe.BANKS;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
// KiB of extended RAM this machine has: 0 on an 800XL or a 65XE, 64
|
|
104
|
+
// on a 130XE. One call at start-up; keep the answer.
|
|
105
|
+
//
|
|
106
|
+
// A machine with more than the 130XE's four banks (a 320XE, a 1088XE)
|
|
107
|
+
// needs more bank bits than the two this reads, and answers 64 here —
|
|
108
|
+
// the four it was asked about.
|
|
109
|
+
function kib(): usmallint {
|
|
110
|
+
let saved: utinyint = portB;
|
|
111
|
+
banks.base(saved);
|
|
112
|
+
memory.write(Xe.WINDOW, Xe.BASE_MARK);
|
|
113
|
+
banks.markAll(saved);
|
|
114
|
+
let found: utinyint = 0;
|
|
115
|
+
// Base RAM still holding its own marker is what says the bank
|
|
116
|
+
// writes went somewhere else at all.
|
|
117
|
+
if (memory.read(Xe.WINDOW) == Xe.BASE_MARK) {
|
|
118
|
+
found = banks.kept(saved);
|
|
119
|
+
}
|
|
120
|
+
portB = saved;
|
|
121
|
+
let k: usmallint = found;
|
|
122
|
+
return k * 16;
|
|
123
|
+
}
|
|
124
|
+
}
|
package/src/console.8bs
ADDED
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
// @8bitscript/atari8/console — START, SELECT, OPTION and the console speaker.
|
|
2
|
+
//
|
|
3
|
+
// Named by "8bitscript".exports["./console"] in this package's package.json:
|
|
4
|
+
//
|
|
5
|
+
// import { console, ConsoleKey } from "@8bitscript/atari8/console";
|
|
6
|
+
//
|
|
7
|
+
// console.scan();
|
|
8
|
+
// if (console.pressed(ConsoleKey.START)) { ... }
|
|
9
|
+
//
|
|
10
|
+
// The three keys to the right of the keyboard are not on the keyboard: they
|
|
11
|
+
// are three bits of GTIA's CONSOL ($D01F), active low, read directly with no
|
|
12
|
+
// OS shadow and no interrupt in the way. That makes them the cheapest input
|
|
13
|
+
// on the machine and the reason so many Atari games start on START and cycle
|
|
14
|
+
// difficulty on SELECT — a cartridge with no DOS and no keyboard handler
|
|
15
|
+
// still has these three.
|
|
16
|
+
//
|
|
17
|
+
// CONSOL is the register this project's own `examples/borders`
|
|
18
|
+
// already reads for its OPTION readout; this file is that, named.
|
|
19
|
+
//
|
|
20
|
+
// ---- the fourth bit is an output ------------------------------------------
|
|
21
|
+
//
|
|
22
|
+
// The same register's bit 3 drives the console speaker — the click the OS
|
|
23
|
+
// makes on every keypress. It is not a voice: there is no frequency and no
|
|
24
|
+
// volume, only a cone that moves when the bit changes, so a tone means
|
|
25
|
+
// toggling it in a timed loop and a "click" means toggling it twice. POKEY
|
|
26
|
+
// is where sound lives (./pokey.8bs); this is here because it is the same
|
|
27
|
+
// register and would otherwise be a trap — a program that writes CONSOL to
|
|
28
|
+
// clear something and leaves bit 3 low holds the speaker cone out of centre.
|
|
29
|
+
// `scan()` never writes CONSOL, and `click()` puts the bit back.
|
|
30
|
+
//
|
|
31
|
+
// Reading CONSOL returns the keys; writing it drives the speaker. They are
|
|
32
|
+
// the same address and genuinely different registers, which is why this file
|
|
33
|
+
// declares it twice.
|
|
34
|
+
import { consolRead, consolWrite } from "./index.8bs";
|
|
35
|
+
|
|
36
|
+
// The last scan: `ConsoleKey.*` bits, 1 = pressed, the hardware's active-low
|
|
37
|
+
// sense inverted once here. Zero until the first scan.
|
|
38
|
+
let state: utinyint = 0;
|
|
39
|
+
|
|
40
|
+
export namespace ConsoleKey {
|
|
41
|
+
const START: utinyint = 1;
|
|
42
|
+
const SELECT: utinyint = 2;
|
|
43
|
+
const OPTION: utinyint = 4;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export namespace console {
|
|
47
|
+
// Read the three keys into the snapshot. Once a frame, beside
|
|
48
|
+
// joystick.scan(); unlike the joystick this reads the hardware and so
|
|
49
|
+
// is live whether or not the OS VBI is running.
|
|
50
|
+
function scan(): void {
|
|
51
|
+
state = (consolRead ^ 0x07) & 0x07;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
// One key, from the snapshot: `console.pressed(ConsoleKey.START)`.
|
|
55
|
+
function pressed(key: utinyint): bool {
|
|
56
|
+
return (state & key) != 0;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
// All three at once, as `ConsoleKey.*` bits.
|
|
60
|
+
function bits(): utinyint {
|
|
61
|
+
return state;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
// Move the speaker cone out and back — the OS's keyclick, without the
|
|
65
|
+
// OS. Bit 3 low pushes the cone; the four low bits of a CONSOL write are
|
|
66
|
+
// the ones that matter, and $08 (bit 3 set) is its resting state, which
|
|
67
|
+
// is what this leaves behind. Two calls a frame is a buzz; one on an
|
|
68
|
+
// event is a click. A tone means toggling `speaker()` on a timed loop
|
|
69
|
+
// instead, which costs the CPU the whole time it sounds — POKEY is the
|
|
70
|
+
// answer for anything longer than a click.
|
|
71
|
+
function click(): void {
|
|
72
|
+
consolWrite = 0x00;
|
|
73
|
+
consolWrite = 0x08;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
// The speaker bit on its own, for a program timing its own tone:
|
|
77
|
+
// `out` true pushes the cone, false lets it rest.
|
|
78
|
+
function speaker(out: bool): void {
|
|
79
|
+
if (out) {
|
|
80
|
+
consolWrite = 0x00;
|
|
81
|
+
} else {
|
|
82
|
+
consolWrite = 0x08;
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
}
|
package/src/index.8bs
ADDED
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
// @8bitscript/atari8 — Atari 8-bit target support (400/800/XL/XE/XEGS): the
|
|
2
|
+
// hardware underneath.
|
|
3
|
+
//
|
|
4
|
+
// The module entry point named by the "8bitscript".entry field in this
|
|
5
|
+
// package's package.json, resolved and linked for:
|
|
6
|
+
//
|
|
7
|
+
// import { borderColor, backgroundColor, cursorInhibit } from "@8bitscript/atari8";
|
|
8
|
+
//
|
|
9
|
+
// The surface is the hardware, one register at a time. What a program
|
|
10
|
+
// usually wants sits one layer up, in this package's own implementations of
|
|
11
|
+
// the portable capability packages — `./src/screen.8bs` behind
|
|
12
|
+
// @8bitscript/screen and `./src/text.8bs` behind @8bitscript/text, named by
|
|
13
|
+
// the "8bitscript".exports map in package.json — which import these
|
|
14
|
+
// registers and are built on nothing else.
|
|
15
|
+
//
|
|
16
|
+
// Border and background are two genuinely different GTIA registers here —
|
|
17
|
+
// verified on screen under atari800, not inferred: in ANTIC's standard text
|
|
18
|
+
// mode (the mode the OS boots into, and the one a .XEX inherits), COLBK
|
|
19
|
+
// ($D01A) colours the BORDER around the playfield, while the area behind
|
|
20
|
+
// the characters is COLPF2 ($D018). An earlier version of this package had
|
|
21
|
+
// these the wrong way round: it mapped the background to COLBK and left the
|
|
22
|
+
// border inert, so setting the background visibly painted the *border* and
|
|
23
|
+
// setting the border did nothing at all.
|
|
24
|
+
//
|
|
25
|
+
// Every colour write goes through the OS SHADOW registers as well as the
|
|
26
|
+
// hardware ones — see backgroundColorShadow below for why that is not
|
|
27
|
+
// optional.
|
|
28
|
+
|
|
29
|
+
// $D01A, GTIA's COLBK — the border, in ANTIC's standard text mode.
|
|
30
|
+
@address(0xD01A)
|
|
31
|
+
export let borderColor: volatile<u8>;
|
|
32
|
+
|
|
33
|
+
// $D018, GTIA's COLPF2 — playfield 2, the colour behind the characters in
|
|
34
|
+
// ANTIC's standard text mode.
|
|
35
|
+
@address(0xD018)
|
|
36
|
+
export let backgroundColor: volatile<u8>;
|
|
37
|
+
|
|
38
|
+
// $D017, GTIA's COLPF1 — character luminance in ANTIC mode 2 (GR.0). Hue
|
|
39
|
+
// comes from COLPF2; only the luminance nybble here is visible. Without a
|
|
40
|
+
// write, the OS default is close enough to a dark COLPF2 that letters
|
|
41
|
+
// vanish and only the inverse-video cursor block remains.
|
|
42
|
+
@address(0xD017)
|
|
43
|
+
export let textColor: volatile<u8>;
|
|
44
|
+
|
|
45
|
+
// $02C8 (COLOR4), $02C6 (COLOR2), and $02C5 (COLOR1), the OS SHADOW
|
|
46
|
+
// registers for COLBK, COLPF2, and COLPF1 above — and the ones that
|
|
47
|
+
// actually decide what stays on screen.
|
|
48
|
+
//
|
|
49
|
+
// Writing the hardware registers alone looks correct for about a sixtieth of
|
|
50
|
+
// a second and then silently undoes itself: an Atari program loaded the
|
|
51
|
+
// ordinary way runs with the OS's interrupts live, and the OS's stage-2
|
|
52
|
+
// vertical-blank routine copies its shadow registers in RAM ($02C4-$02C8)
|
|
53
|
+
// over GTIA's colour registers ($D016-$D01A) on every single frame. So the
|
|
54
|
+
// hardware register is not the durable place to put a colour while the OS is
|
|
55
|
+
// running — the shadow is, and the OS then propagates it for us. This is not
|
|
56
|
+
// theoretical: it is exactly why examples/borders rendered as a plain default
|
|
57
|
+
// screen on atari800 before the shadow writes were added. (It is also the
|
|
58
|
+
// same class of problem packages/backend-6502's FRAME_SYNC documents for the
|
|
59
|
+
// PET, where the KERNAL's own IRQ handler wins a race for the vertical-
|
|
60
|
+
// retrace flag; here the OS wins a race for the colour registers, every
|
|
61
|
+
// frame, forever.)
|
|
62
|
+
//
|
|
63
|
+
// A program that takes the machine over completely — disabling the OS VBI —
|
|
64
|
+
// would write only the hardware registers instead. This package's screen.8bs
|
|
65
|
+
// targets the OS-friendly case, so it writes both.
|
|
66
|
+
@address(0x02C8)
|
|
67
|
+
export let borderColorShadow: volatile<u8>;
|
|
68
|
+
|
|
69
|
+
@address(0x02C6)
|
|
70
|
+
export let backgroundColorShadow: volatile<u8>;
|
|
71
|
+
|
|
72
|
+
@address(0x02C5)
|
|
73
|
+
export let textColorShadow: volatile<u8>;
|
|
74
|
+
|
|
75
|
+
// $02F0, CRSINH — set non-zero to stop the OS screen editor drawing its
|
|
76
|
+
// inverse-video cursor over whatever cell it sits on. Both
|
|
77
|
+
// `screen.setColors()` and `text.putChar()` set it, so a program gets a
|
|
78
|
+
// cursor-free screen whichever of the two it calls first.
|
|
79
|
+
@address(0x02F0)
|
|
80
|
+
export let cursorInhibit: volatile<u8>;
|
|
81
|
+
|
|
82
|
+
// ---- the registers the layers above this file are built on ------------------
|
|
83
|
+
//
|
|
84
|
+
// Everything below is imported by one of this package's own hardware layers —
|
|
85
|
+
// ./joystick.8bs, ./console.8bs, ./keyboard.8bs, ./pokey.8bs, ./random.8bs —
|
|
86
|
+
// and a program that imports none of them links none of this. Each layer's
|
|
87
|
+
// own file says what it does with these and why; this file only says where
|
|
88
|
+
// they are.
|
|
89
|
+
//
|
|
90
|
+
// Two of them are the same address twice, which is not a mistake. On this
|
|
91
|
+
// machine several I/O locations are one register when read and a completely
|
|
92
|
+
// different one when written — GTIA's CONSOL, POKEY's KBCODE/STIMER and
|
|
93
|
+
// SKSTAT/SKCTL — so each direction gets its own name and the layer that uses
|
|
94
|
+
// it cannot pick the wrong one by accident.
|
|
95
|
+
|
|
96
|
+
// $004D, ATRACT — the OS's attract-mode counter. Its VBI counts this up and,
|
|
97
|
+
// once it passes a threshold (roughly nine minutes), starts cycling the
|
|
98
|
+
// playfield colours to spare the phosphor. The OS zeroes it on a keypress, so
|
|
99
|
+
// only a program read with a joystick has to; @8bitscript/atari8/joystick's
|
|
100
|
+
// scan() does, every frame.
|
|
101
|
+
@address(0x004D)
|
|
102
|
+
export let attract: volatile<u8>;
|
|
103
|
+
|
|
104
|
+
// $02FC, CH — the last key the OS's keyboard handler decoded, $FF for
|
|
105
|
+
// "nothing new". A program consumes a key by writing $FF back.
|
|
106
|
+
@address(0x02FC)
|
|
107
|
+
export let chShadow: volatile<u8>;
|
|
108
|
+
|
|
109
|
+
// $D01F read: GTIA's CONSOL as the three console keys, bits 0/1/2 =
|
|
110
|
+
// START/SELECT/OPTION, 0 while pressed.
|
|
111
|
+
@address(0xD01F)
|
|
112
|
+
export let consolRead: volatile<u8>;
|
|
113
|
+
|
|
114
|
+
// $D01F written: the same address as the console speaker. Bit 3 low pushes
|
|
115
|
+
// the cone; $08 is its resting state.
|
|
116
|
+
@address(0xD01F)
|
|
117
|
+
export let consolWrite: volatile<u8>;
|
|
118
|
+
|
|
119
|
+
// $D209 read: POKEY's KBCODE, the raw scan code of the last key, with SHIFT
|
|
120
|
+
// in bit 6 and CTRL in bit 7. (Writing this address is STIMER instead, which
|
|
121
|
+
// nothing here does.)
|
|
122
|
+
@address(0xD209)
|
|
123
|
+
export let kbcode: volatile<u8>;
|
|
124
|
+
|
|
125
|
+
// $D20F read: POKEY's SKSTAT. Its keyboard bits are active low — bit 2 is 0
|
|
126
|
+
// while the last key is still held, bit 3 is 0 while SHIFT is held. (Writing
|
|
127
|
+
// this address is SKCTL, which nothing here does: the OS leaves it at $03 to
|
|
128
|
+
// keep POKEY's keyboard scan and debounce running, and a program that
|
|
129
|
+
// overwrites it breaks its own keyboard.)
|
|
130
|
+
@address(0xD20F)
|
|
131
|
+
export let skstat: volatile<u8>;
|
|
132
|
+
|
|
133
|
+
// $D208, POKEY's AUDCTL — the global audio control: clock choice, the
|
|
134
|
+
// high-pass links, the 16-bit channel joins. Per-voice AUDF/AUDC are
|
|
135
|
+
// $D200-$D207 and @8bitscript/atari8/pokey reaches them by offset from a
|
|
136
|
+
// base rather than as eight names.
|
|
137
|
+
@address(0xD208)
|
|
138
|
+
export let audctl: volatile<u8>;
|
|
139
|
+
|
|
140
|
+
// $D20A read: POKEY's RANDOM, a sample of the free-running 17-bit polynomial
|
|
141
|
+
// counter. Behind @8bitscript/atari8/random and deliberately nowhere else.
|
|
142
|
+
@address(0xD20A)
|
|
143
|
+
export let randomRegister: volatile<u8>;
|
|
144
|
+
|
|
145
|
+
// $D014 read: GTIA's PAL register, the TV standard this machine was built
|
|
146
|
+
// for. A PAL machine answers 1 in the low bits, an NTSC one 14 or 15
|
|
147
|
+
// (atari800 returns $01 and $0F). One binary reads it at start-up and adapts,
|
|
148
|
+
// the way the frame runtime already does.
|
|
149
|
+
@address(0xD014)
|
|
150
|
+
export let palRegister: volatile<u8>;
|
package/src/input.8bs
ADDED
|
@@ -0,0 +1,178 @@
|
|
|
1
|
+
// @8bitscript/atari8/input — the Atari 8-bit behind @8bitscript/input.
|
|
2
|
+
//
|
|
3
|
+
// Named by "8bitscript".exports["./input"] in this package's package.json,
|
|
4
|
+
// and by "8bitscript".entry.atari8 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 400, 800, XL or XE. The layers underneath —
|
|
10
|
+
// ./keyboard.8bs with ./keys.8bs, ./joystick.8bs, ./console.8bs — stay
|
|
11
|
+
// available to a program that wants what this file does not map.
|
|
12
|
+
//
|
|
13
|
+
// ---- four directions out of a keyboard that has no arrow keys ------------
|
|
14
|
+
//
|
|
15
|
+
// This machine has no arrow keys. It has four *keys with arrows printed on
|
|
16
|
+
// them* — `+` `*` `-` `=` — which the OS turns into cursor movement only
|
|
17
|
+
// when CTRL is held, and that is genuinely how an Atari user moves a
|
|
18
|
+
// cursor. So:
|
|
19
|
+
//
|
|
20
|
+
// - **left** CTRL and `+`, **right** CTRL and `*`,
|
|
21
|
+
// **up** CTRL and `-`, **down** CTRL and `=`.
|
|
22
|
+
// - **confirm** — RETURN, or the joystick's fire button.
|
|
23
|
+
// - **cancel** — ESC.
|
|
24
|
+
//
|
|
25
|
+
// A joystick in **port 1** is or'd on top of the keyboard, so a program
|
|
26
|
+
// does not care which the user reached for. Ports 3 and 4 exist only on a
|
|
27
|
+
// 400 or an 800 (`Joystick.PORTS` is the fact), and this file reads
|
|
28
|
+
// neither: the portable surface describes one user driving one interface.
|
|
29
|
+
//
|
|
30
|
+
// The console keys — START, SELECT, OPTION — are *not* mapped here. They
|
|
31
|
+
// are the machine's own three buttons and a program that wants them means
|
|
32
|
+
// them specifically; folding them into `confirm()` would make a menu
|
|
33
|
+
// commit when someone reached for the one that usually means "choose
|
|
34
|
+
// something else". ./console.8bs is one import away.
|
|
35
|
+
//
|
|
36
|
+
// ---- the mouse this file does not drive ----------------------------------
|
|
37
|
+
//
|
|
38
|
+
// **The catalog offers three, and none of them has a driver yet.**
|
|
39
|
+
// `--hardware mouse=st`, `=amiga` and `=trak` all set `input.mouse` true,
|
|
40
|
+
// so `#fact(input.mouse)` says this build may use one — and this file
|
|
41
|
+
// still answers `pointer()` false, because saying "maybe" and then never
|
|
42
|
+
// finding one is worse than saying no.
|
|
43
|
+
//
|
|
44
|
+
// What is missing is a real driver, and it is not the C64's. An ST or
|
|
45
|
+
// Amiga mouse on this machine is **quadrature on the joystick port's four
|
|
46
|
+
// direction lines** — two bits per axis whose *transition order* gives the
|
|
47
|
+
// direction — read through PORTA, not through an analogue converter. The
|
|
48
|
+
// Atari trak-ball is a third protocol again, and a 1350-style mouse would
|
|
49
|
+
// be a fourth. That is a per-device driver in this package with its own
|
|
50
|
+
// `detect`, sitting beside ./joystick.8bs, and then three lines here.
|
|
51
|
+
// Until someone writes and verifies it against atari800, this file says
|
|
52
|
+
// what is true: no pointer.
|
|
53
|
+
//
|
|
54
|
+
// ---- edges, not levels ---------------------------------------------------
|
|
55
|
+
//
|
|
56
|
+
// Every answer is **edge-triggered**: true on the one frame the press
|
|
57
|
+
// begins, false while it is held. `poll()` must be called exactly once a
|
|
58
|
+
// frame, right after waitFrame().
|
|
59
|
+
//
|
|
60
|
+
// One caution this machine brings that the Commodores do not: its keyboard
|
|
61
|
+
// hardware reports **one key code at a time**, not a matrix — so "CTRL and
|
|
62
|
+
// `+`" is the CTRL *modifier bit* alongside the last key code, which the
|
|
63
|
+
// hardware does give, but two ordinary keys at once is not a question this
|
|
64
|
+
// machine can answer. That is a property of the hardware, not of this file.
|
|
65
|
+
import { keyboard } from "./keyboard.8bs";
|
|
66
|
+
import { Key } from "./keys.8bs";
|
|
67
|
+
import { joystick, Joystick } from "./joystick.8bs";
|
|
68
|
+
|
|
69
|
+
namespace Edge {
|
|
70
|
+
const LEFT: utinyint = 1;
|
|
71
|
+
const RIGHT: utinyint = 2;
|
|
72
|
+
const UP: utinyint = 4;
|
|
73
|
+
const DOWN: utinyint = 8;
|
|
74
|
+
const CONFIRM: utinyint = 16;
|
|
75
|
+
const CANCEL: utinyint = 32;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
let held: utinyint = 0;
|
|
79
|
+
let before: utinyint = 0;
|
|
80
|
+
let began: utinyint = 0;
|
|
81
|
+
|
|
82
|
+
export namespace input {
|
|
83
|
+
|
|
84
|
+
// Nothing to set up: no pointer to give a ceiling to. Here so a
|
|
85
|
+
// program written for nine machines calls the same thing on all.
|
|
86
|
+
function begin(): void {
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
// Read the key register and the port, and work out what began this
|
|
90
|
+
// frame. Once a frame, right after waitFrame().
|
|
91
|
+
function poll(): void {
|
|
92
|
+
keyboard.scan();
|
|
93
|
+
joystick.scan();
|
|
94
|
+
|
|
95
|
+
before = held;
|
|
96
|
+
held = 0;
|
|
97
|
+
|
|
98
|
+
// The arrows are CTRL plus a printed key — asked once and reused,
|
|
99
|
+
// because the modifier is a separate bit from the key code.
|
|
100
|
+
if (keyboard.control()) {
|
|
101
|
+
if (keyboard.pressed(Key.PLUS)) {
|
|
102
|
+
held = held | Edge.LEFT;
|
|
103
|
+
}
|
|
104
|
+
if (keyboard.pressed(Key.ASTERISK)) {
|
|
105
|
+
held = held | Edge.RIGHT;
|
|
106
|
+
}
|
|
107
|
+
if (keyboard.pressed(Key.DASH)) {
|
|
108
|
+
held = held | Edge.UP;
|
|
109
|
+
}
|
|
110
|
+
if (keyboard.pressed(Key.EQUALS)) {
|
|
111
|
+
held = held | Edge.DOWN;
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
if (keyboard.pressed(Key.RETURN)) {
|
|
115
|
+
held = held | Edge.CONFIRM;
|
|
116
|
+
}
|
|
117
|
+
if (keyboard.pressed(Key.ESC)) {
|
|
118
|
+
held = held | Edge.CANCEL;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
let stick: utinyint = joystick.bits(Joystick.PORT_1);
|
|
122
|
+
if ((stick & Joystick.LEFT) != 0) {
|
|
123
|
+
held = held | Edge.LEFT;
|
|
124
|
+
}
|
|
125
|
+
if ((stick & Joystick.RIGHT) != 0) {
|
|
126
|
+
held = held | Edge.RIGHT;
|
|
127
|
+
}
|
|
128
|
+
if ((stick & Joystick.UP) != 0) {
|
|
129
|
+
held = held | Edge.UP;
|
|
130
|
+
}
|
|
131
|
+
if ((stick & Joystick.DOWN) != 0) {
|
|
132
|
+
held = held | Edge.DOWN;
|
|
133
|
+
}
|
|
134
|
+
if ((stick & Joystick.FIRE) != 0) {
|
|
135
|
+
held = held | Edge.CONFIRM;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
began = held & (before ^ 0xFF);
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
function left(): bool {
|
|
142
|
+
return (began & Edge.LEFT) != 0;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
function right(): bool {
|
|
146
|
+
return (began & Edge.RIGHT) != 0;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
function up(): bool {
|
|
150
|
+
return (began & Edge.UP) != 0;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
function down(): bool {
|
|
154
|
+
return (began & Edge.DOWN) != 0;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
function confirm(): bool {
|
|
158
|
+
return (began & Edge.CONFIRM) != 0;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
function cancel(): bool {
|
|
162
|
+
return (began & Edge.CANCEL) != 0;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
// No driver for any of the three mice the catalog offers — see the
|
|
166
|
+
// header. False rather than "maybe", and a constant either way.
|
|
167
|
+
function pointer(): bool {
|
|
168
|
+
return false;
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
function pointerCell(): usmallint {
|
|
172
|
+
return 0;
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
function pointerButton(): bool {
|
|
176
|
+
return false;
|
|
177
|
+
}
|
|
178
|
+
}
|