@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/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
+ }
@@ -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
+ }