jsbeeb 1.22.4 → 1.24.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/README.md +2 -2
- package/package.json +16 -11
- package/src/6502.js +39 -42
- package/src/6502.opcodes.js +198 -123
- package/src/6847.js +4 -5
- package/src/6847_fontdata.js +0 -2
- package/src/acia.js +2 -3
- package/src/app/app.js +0 -1
- package/src/app/args.js +0 -2
- package/src/app/electron.js +4 -6
- package/src/app-bench.js +1 -3
- package/src/archive.js +243 -0
- package/src/basic-loader.js +0 -7
- package/src/basic-tokenise.js +19 -6
- package/src/bbc-palette.js +0 -2
- package/src/bbcdiscs.js +0 -2
- package/src/bem-snapshot.js +1 -3
- package/src/binary.js +175 -0
- package/src/cmos.js +0 -2
- package/src/ddnoise.js +0 -1
- package/src/debounce.js +7 -0
- package/src/disc-surface.js +0 -2
- package/src/disc.js +10 -9
- package/src/fake6502.js +7 -23
- package/src/fdc.js +9 -51
- package/src/filestore.js +10 -17
- package/src/hex.js +38 -0
- package/src/index.js +11 -0
- package/src/intel-fdc.js +18 -20
- package/src/jsbeeb.css +33 -0
- package/src/{utils_atom.js → keymap-atom.js} +2 -18
- package/src/{utils.js → keymap.js} +47 -610
- package/src/loader.js +63 -0
- package/src/machine-session.js +37 -177
- package/src/machine-spec.js +59 -0
- package/src/main.js +69 -184
- package/src/media-resolver.js +68 -0
- package/src/mmc.js +3 -2
- package/src/models.js +40 -4
- package/src/music5000.js +0 -2
- package/src/ppia.js +3 -4
- package/src/printer.js +7 -14
- package/src/relaynoise.js +0 -1
- package/src/rewind.js +0 -2
- package/src/sample-player.js +2 -3
- package/src/serial.js +0 -2
- package/src/snapshot-helpers.js +0 -2
- package/src/snapshot.js +0 -2
- package/src/soundchip.js +1 -1
- package/src/state-utils.js +0 -2
- package/src/sth.js +2 -4
- package/src/tapes.js +10 -4
- package/src/teletext.js +2 -3
- package/src/teletext_adaptor.js +2 -4
- package/src/teletext_data.js +0 -2
- package/src/test-machine.js +307 -0
- package/src/touchscreen.js +2 -4
- package/src/tube.js +5 -6
- package/src/typist.js +103 -0
- package/src/uef-snapshot.js +0 -2
- package/src/url-params.js +4 -5
- package/src/vdu-capture.js +113 -0
- package/src/via.js +3 -4
- package/src/video-filters/pal-composite.js +0 -2
- package/src/video-filters/passthrough-filter.js +0 -2
- package/src/video-filters/pixel-grid.js +0 -2
- package/src/video-filters/shader-program.js +0 -2
- package/src/video-filters/xbr-filter.js +0 -2
- package/src/video.js +12 -11
- package/src/wd-fdc.js +20 -20
- package/src/web/analogue-inputs.js +6 -8
- package/src/web/analytics.js +13 -0
- package/src/web/audio-handler.js +2 -2
- package/src/{audio-utils.js → web/audio-utils.js} +0 -2
- package/src/web/autoboot.js +10 -13
- package/src/{canvas.js → web/canvas.js} +4 -5
- package/src/web/config.js +42 -72
- package/src/web/console-surface.js +25 -0
- package/src/web/debug.js +8 -49
- package/src/web/disc-visualiser.js +0 -2
- package/src/web/display.js +21 -4
- package/src/{dom-utils.js → web/dom-utils.js} +1 -11
- package/src/web/drives.js +1 -1
- package/src/web/emulation-loop.js +54 -33
- package/src/web/front-panel.js +13 -12
- package/src/{gamepads.js → web/gamepads.js} +2 -6
- package/src/web/google-drive-picker.js +14 -9
- package/src/{google-drive.js → web/google-drive.js} +9 -10
- package/src/web/hfe-picker.js +5 -4
- package/src/web/icons.js +42 -0
- package/src/web/keyboard-setup.js +29 -40
- package/src/{keyboard.js → web/keyboard.js} +18 -119
- package/src/web/layout.js +4 -0
- package/src/web/local-disc.js +44 -0
- package/src/web/machine.js +27 -20
- package/src/web/media-loader.js +38 -147
- package/src/{microphone-input.js → web/microphone-input.js} +1 -1
- package/src/web/modals.js +6 -9
- package/src/web/page-actions.js +88 -0
- package/src/web/quick-settings.js +16 -15
- package/src/web/reporting.js +0 -7
- package/src/{rewind-thumbnail.js → web/rewind-thumbnail.js} +0 -2
- package/src/web/rewind-ui.js +29 -29
- package/src/web/run-controls.js +39 -0
- package/src/web/settings.js +64 -135
- package/src/web/snapshot-ui.js +10 -16
- package/src/{speech-output.js → web/speech-output.js} +0 -2
- package/src/web/sth-picker.js +9 -7
- package/src/web/url-state.js +20 -6
- package/src/econet/L3FS.dat +0 -0
- package/src/econet/scsi.dat +0 -0
- package/tests/test-machine.js +0 -609
package/README.md
CHANGED
|
@@ -96,8 +96,8 @@ Some things to know:
|
|
|
96
96
|
was at fault.
|
|
97
97
|
- On the Atom, use the Atom's own key names (`LOCK`, `UP_DOWN`, `LEFT_RIGHT` and so on) rather than the BBC's.
|
|
98
98
|
|
|
99
|
-
The definitive lists are `keyCodes` (host) and `BBC` (BBC micro) in [`src/
|
|
100
|
-
[`src/
|
|
99
|
+
The definitive lists are `keyCodes` (host) and `BBC` (BBC micro) in [`src/keymap.js`](src/keymap.js), and `ATOM` in
|
|
100
|
+
[`src/keymap-atom.js`](src/keymap-atom.js).
|
|
101
101
|
|
|
102
102
|
### Emulator Shortcuts
|
|
103
103
|
|
package/package.json
CHANGED
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
"name": "jsbeeb",
|
|
8
8
|
"description": "Emulate a BBC Micro",
|
|
9
9
|
"repository": "git@github.com:mattgodbolt/jsbeeb.git",
|
|
10
|
-
"version": "1.
|
|
10
|
+
"version": "1.24.0",
|
|
11
11
|
"//engines": "If you change the version of Node, it must also be updated at the top of the Dockerfile.",
|
|
12
12
|
"engines": {
|
|
13
13
|
"node": ">=24.15.0"
|
|
@@ -15,27 +15,26 @@
|
|
|
15
15
|
"type": "module",
|
|
16
16
|
"main": "./src/app/app.js",
|
|
17
17
|
"exports": {
|
|
18
|
-
".": "./src/
|
|
18
|
+
".": "./src/index.js",
|
|
19
19
|
"./machine-session": "./src/machine-session.js",
|
|
20
|
-
"./src/*": "./src/*"
|
|
21
|
-
"./tests/test-machine.js": "./tests/test-machine.js"
|
|
20
|
+
"./src/*": "./src/*"
|
|
22
21
|
},
|
|
23
22
|
"files": [
|
|
24
23
|
"src/",
|
|
25
|
-
"tests/test-machine.js",
|
|
26
24
|
"public/roms/"
|
|
27
25
|
],
|
|
28
26
|
"dependencies": {
|
|
29
27
|
"@popperjs/core": "^2.11.8",
|
|
30
28
|
"argparse": "^3.0.0",
|
|
31
29
|
"bootstrap": "^5.3.8",
|
|
30
|
+
"bootstrap-icons": "^1.13.1",
|
|
32
31
|
"bootswatch": "^5.3.8",
|
|
33
32
|
"pako": "^3.0.1",
|
|
34
|
-
"sharp": "^0.35.3",
|
|
35
33
|
"smoothie": "^1.36.1"
|
|
36
34
|
},
|
|
37
35
|
"devDependencies": {
|
|
38
36
|
"@eslint/js": "^10.0.1",
|
|
37
|
+
"@playwright/test": "^1.62.1",
|
|
39
38
|
"@vitest/coverage-v8": "^4.0.18",
|
|
40
39
|
"eslint": "^10.7.0",
|
|
41
40
|
"eslint-config-prettier": "^10.1.8",
|
|
@@ -52,7 +51,8 @@
|
|
|
52
51
|
"vitest": "^4.0.10"
|
|
53
52
|
},
|
|
54
53
|
"optionalDependencies": {
|
|
55
|
-
"electron": "^
|
|
54
|
+
"electron": "^44.0.0",
|
|
55
|
+
"sharp": "^0.35.3",
|
|
56
56
|
"electron-builder": "^26.15.3",
|
|
57
57
|
"electron-store": "^11.0.2"
|
|
58
58
|
},
|
|
@@ -89,9 +89,12 @@
|
|
|
89
89
|
"start": "vite",
|
|
90
90
|
"build": "vite build",
|
|
91
91
|
"preview": "vite preview",
|
|
92
|
-
"ci-checks": "
|
|
93
|
-
"
|
|
92
|
+
"ci-checks": "npm-run-all lint format:check check-workflow-suites",
|
|
93
|
+
"check-workflow-suites": "node tools/check-workflow-suites.js",
|
|
94
|
+
"lint": "eslint .",
|
|
95
|
+
"lint-fix": "eslint . --fix",
|
|
94
96
|
"format": "prettier --write .",
|
|
97
|
+
"format:check": "prettier --check .",
|
|
95
98
|
"pre-commit": "lint-staged",
|
|
96
99
|
"test": "npm-run-all test:*",
|
|
97
100
|
"test:unit": "vitest run --silent=true tests/unit",
|
|
@@ -99,6 +102,7 @@
|
|
|
99
102
|
"test:cpu": "node tests/test-suite.js",
|
|
100
103
|
"test:shader": "vitest run --silent=true tests/shader",
|
|
101
104
|
"coverage:unit": "vitest run tests/unit --coverage",
|
|
105
|
+
"dom-unit-tests": "vitest run --silent=true --project unit $(grep -rl domFromIndexHtml tests/unit)",
|
|
102
106
|
"coverage:all-tests": "vitest run --coverage",
|
|
103
107
|
"benchmark": "node src/app-bench.js",
|
|
104
108
|
"mirror-sth": "node tools/mirror-sth.js --out .sth-mirror",
|
|
@@ -115,14 +119,15 @@
|
|
|
115
119
|
"mirror-bbcdiscs:upload:index": "aws s3 cp .bbcdiscs-mirror/hfe/manifest.json s3://bbc.xania.org/archive/bbcdiscs/hfe/manifest.json --cache-control 'public, max-age=300' && aws s3 cp .bbcdiscs-mirror/manifest.json s3://bbc.xania.org/archive/bbcdiscs/manifest.json --cache-control 'public, max-age=300'",
|
|
116
120
|
"electron": "npm run build && ELECTRON_DISABLE_SANDBOX=1 electron .",
|
|
117
121
|
"electron:build": "electron-builder",
|
|
118
|
-
"test:
|
|
122
|
+
"test:e2e": "npm run build && playwright test --project chrome"
|
|
119
123
|
},
|
|
120
124
|
"lint-staged": {
|
|
121
125
|
"*.js": [
|
|
122
|
-
"vitest related --run --
|
|
126
|
+
"vitest related --run --project unit --no-file-parallelism",
|
|
123
127
|
"eslint --cache --fix",
|
|
124
128
|
"prettier --write"
|
|
125
129
|
],
|
|
130
|
+
"index.html": "npm run dom-unit-tests --",
|
|
126
131
|
"*.{md,json,yml,yaml,css,html}": "prettier --write"
|
|
127
132
|
},
|
|
128
133
|
"allowScripts": {
|
package/src/6502.js
CHANGED
|
@@ -1,5 +1,3 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
import * as utils from "./utils.js";
|
|
3
1
|
import * as via from "./via.js";
|
|
4
2
|
import { Acia } from "./acia.js";
|
|
5
3
|
import { Serial } from "./serial.js";
|
|
@@ -12,8 +10,10 @@ import { Filestore } from "./filestore.js";
|
|
|
12
10
|
import { FakeRelayNoise } from "./relaynoise.js";
|
|
13
11
|
import { AtomPPIA } from "./ppia.js";
|
|
14
12
|
import { AtomMMC2 } from "./mmc.js";
|
|
15
|
-
|
|
16
|
-
|
|
13
|
+
import { unzipRomImage } from "./archive.js";
|
|
14
|
+
import { signExtend } from "./binary.js";
|
|
15
|
+
import { hexbyte, hexword } from "./hex.js";
|
|
16
|
+
import { loadData } from "./loader.js";
|
|
17
17
|
|
|
18
18
|
function _set(byte, mask, set) {
|
|
19
19
|
return (byte & ~mask) | (set ? mask : 0);
|
|
@@ -255,7 +255,7 @@ class Base6502 {
|
|
|
255
255
|
this.polltime(3);
|
|
256
256
|
} else {
|
|
257
257
|
this.polltime(2);
|
|
258
|
-
// TODO
|
|
258
|
+
// TODO(#1063) check 65c12 BRK interrupt poll timing.
|
|
259
259
|
this.checkInt();
|
|
260
260
|
this.polltime(1);
|
|
261
261
|
}
|
|
@@ -540,7 +540,7 @@ class Tube6502 extends Base6502 {
|
|
|
540
540
|
async loadOs() {
|
|
541
541
|
console.log("Loading tube rom from roms/" + this.model.os);
|
|
542
542
|
const tubeRom = this.rom;
|
|
543
|
-
const data = await
|
|
543
|
+
const data = await loadData("roms/" + this.model.os);
|
|
544
544
|
const len = data.length;
|
|
545
545
|
if (len !== 2048) throw new Error("Broken ROM file (length=" + len + ")");
|
|
546
546
|
for (let i = 0; i < len; ++i) {
|
|
@@ -561,25 +561,6 @@ class FakeTube {
|
|
|
561
561
|
reset() {}
|
|
562
562
|
}
|
|
563
563
|
|
|
564
|
-
class FakeUserPort {
|
|
565
|
-
write() {}
|
|
566
|
-
|
|
567
|
-
read() {
|
|
568
|
-
return 0xff;
|
|
569
|
-
}
|
|
570
|
-
}
|
|
571
|
-
|
|
572
|
-
function fixUpConfig(config) {
|
|
573
|
-
if (config === undefined) config = {};
|
|
574
|
-
if (!config.keyLayout) config.keyLayout = "physical";
|
|
575
|
-
if (!config.cpuMultiplier) config.cpuMultiplier = 1;
|
|
576
|
-
if (!config.userPort) config.userPort = new FakeUserPort();
|
|
577
|
-
if (config.printerPort === undefined) config.printerPort = null;
|
|
578
|
-
config.extraRoms = config.extraRoms || [];
|
|
579
|
-
config.debugFlags = config.debugFlags || {};
|
|
580
|
-
return config;
|
|
581
|
-
}
|
|
582
|
-
|
|
583
564
|
class DebugHook {
|
|
584
565
|
constructor(cpu, functionName) {
|
|
585
566
|
this.cpu = cpu;
|
|
@@ -645,7 +626,8 @@ export class Cpu6502 extends Base6502 {
|
|
|
645
626
|
} = {},
|
|
646
627
|
) {
|
|
647
628
|
super(model, { cycleAccurate });
|
|
648
|
-
this.config =
|
|
629
|
+
this.config = config;
|
|
630
|
+
this.keyLayout = config.keyLayout;
|
|
649
631
|
this.debugFlags = this.config.debugFlags;
|
|
650
632
|
this.cmos = cmos;
|
|
651
633
|
this.debugger = dbgr;
|
|
@@ -702,12 +684,14 @@ export class Cpu6502 extends Base6502 {
|
|
|
702
684
|
soundChip: this.soundChip,
|
|
703
685
|
cmos: this.cmos,
|
|
704
686
|
isMaster: this.model.isMaster,
|
|
705
|
-
initialLayout: this.
|
|
687
|
+
initialLayout: this.keyLayout,
|
|
706
688
|
getGamepads: this.config.getGamepads,
|
|
707
689
|
});
|
|
708
690
|
this.uservia = new via.UserVia(this, this.scheduler, this.model.isMaster, this.config.userPort);
|
|
709
691
|
this.acia = new Acia(this, this.soundChip.toneGenerator, this.scheduler, this.relayNoise);
|
|
710
692
|
this.serial = new Serial(this.acia);
|
|
693
|
+
this.keyboardInterface = this.sysvia;
|
|
694
|
+
this.tapeInterface = this.acia;
|
|
711
695
|
this.adconverter = new Adc(this.sysvia, this.scheduler);
|
|
712
696
|
this.touchScreen = new TouchScreen(this.scheduler, this.model.cyclesPerSecond);
|
|
713
697
|
this.soundChip.setScheduler(this.scheduler);
|
|
@@ -826,7 +810,7 @@ export class Cpu6502 extends Base6502 {
|
|
|
826
810
|
readArea(addr, len) {
|
|
827
811
|
let str = "";
|
|
828
812
|
for (let i = 0; i < len; ++i) {
|
|
829
|
-
str +=
|
|
813
|
+
str += hexbyte(this.readmem(addr + i));
|
|
830
814
|
}
|
|
831
815
|
return str;
|
|
832
816
|
}
|
|
@@ -1003,7 +987,7 @@ export class Cpu6502 extends Base6502 {
|
|
|
1003
987
|
const offset = this.memLook[statOffset];
|
|
1004
988
|
return this.ramRomOs[offset + addr];
|
|
1005
989
|
} else {
|
|
1006
|
-
return 0xff; // TODO
|
|
990
|
+
return 0xff; // TODO(#1062) peekDevice: this.peekDevice(addr);
|
|
1007
991
|
}
|
|
1008
992
|
}
|
|
1009
993
|
|
|
@@ -1165,9 +1149,9 @@ export class Cpu6502 extends Base6502 {
|
|
|
1165
1149
|
if (name.indexOf("http") !== 0) name = "roms/" + name;
|
|
1166
1150
|
console.log("Loading ROM from " + name);
|
|
1167
1151
|
const ramRomOs = this.ramRomOs;
|
|
1168
|
-
let data = await
|
|
1152
|
+
let data = await loadData(name);
|
|
1169
1153
|
if (/\.zip/i.test(name)) {
|
|
1170
|
-
data = (await
|
|
1154
|
+
data = (await unzipRomImage(data)).data;
|
|
1171
1155
|
}
|
|
1172
1156
|
ramRomOs.set(data, offset);
|
|
1173
1157
|
}
|
|
@@ -1177,7 +1161,7 @@ export class Cpu6502 extends Base6502 {
|
|
|
1177
1161
|
os = "roms/" + os;
|
|
1178
1162
|
console.log(`Loading OS from ${os}`);
|
|
1179
1163
|
const ramRomOs = this.ramRomOs;
|
|
1180
|
-
const data = await
|
|
1164
|
+
const data = await loadData(os);
|
|
1181
1165
|
const len = data.length;
|
|
1182
1166
|
if (len < 16384 || len & 16383) throw new Error(`Broken OS ROM file (length=${len})`);
|
|
1183
1167
|
ramRomOs.set(data, this.osOffset);
|
|
@@ -1410,7 +1394,7 @@ export class Cpu6502 extends Base6502 {
|
|
|
1410
1394
|
}
|
|
1411
1395
|
|
|
1412
1396
|
updateKeyLayout() {
|
|
1413
|
-
this.sysvia.setKeyLayout(this.
|
|
1397
|
+
this.sysvia.setKeyLayout(this.keyLayout);
|
|
1414
1398
|
}
|
|
1415
1399
|
|
|
1416
1400
|
polltimeAddr(cycles, addr, isWrite) {
|
|
@@ -1555,6 +1539,12 @@ export class Cpu6502 extends Base6502 {
|
|
|
1555
1539
|
this.halted = true;
|
|
1556
1540
|
}
|
|
1557
1541
|
|
|
1542
|
+
/** Changes the host key layout for the keyboard, and for every reset after it. */
|
|
1543
|
+
setKeyLayout(layout) {
|
|
1544
|
+
this.keyLayout = layout;
|
|
1545
|
+
this.sysvia.setKeyLayout(layout);
|
|
1546
|
+
}
|
|
1547
|
+
|
|
1558
1548
|
dumpTrace(maxToShow, func) {
|
|
1559
1549
|
if (!maxToShow) maxToShow = 256;
|
|
1560
1550
|
if (maxToShow > 256) maxToShow = 256;
|
|
@@ -1564,11 +1554,11 @@ export class Cpu6502 extends Base6502 {
|
|
|
1564
1554
|
function (pc, a, x, y) {
|
|
1565
1555
|
const dis = disassembler.disassemble(pc, true)[0];
|
|
1566
1556
|
console.log(
|
|
1567
|
-
|
|
1557
|
+
hexword(pc),
|
|
1568
1558
|
(dis + " ").substring(0, 15),
|
|
1569
|
-
|
|
1570
|
-
|
|
1571
|
-
|
|
1559
|
+
hexbyte(a),
|
|
1560
|
+
hexbyte(x),
|
|
1561
|
+
hexbyte(y),
|
|
1572
1562
|
);
|
|
1573
1563
|
};
|
|
1574
1564
|
for (let i = maxToShow - 2; i >= 0; --i) {
|
|
@@ -1613,12 +1603,19 @@ const NumBranquartBanks = 16;
|
|
|
1613
1603
|
const AtomRomBlockSize = 0x1000; // 4KB
|
|
1614
1604
|
|
|
1615
1605
|
export class AtomCpu6502 extends Cpu6502 {
|
|
1606
|
+
setKeyLayout(layout) {
|
|
1607
|
+
this.keyLayout = layout;
|
|
1608
|
+
this.atomppia.setKeyLayout(layout);
|
|
1609
|
+
}
|
|
1610
|
+
|
|
1616
1611
|
constructor(model, options) {
|
|
1617
1612
|
super(model, options);
|
|
1618
1613
|
|
|
1619
1614
|
// Atom peripherals
|
|
1620
|
-
this.atomppia = new AtomPPIA(this, this.
|
|
1615
|
+
this.atomppia = new AtomPPIA(this, this.keyLayout, this.scheduler);
|
|
1621
1616
|
this.atommc = new AtomMMC2(this);
|
|
1617
|
+
this.keyboardInterface = this.atomppia;
|
|
1618
|
+
this.tapeInterface = this.atomppia;
|
|
1622
1619
|
|
|
1623
1620
|
// Branquart bank selection
|
|
1624
1621
|
this.branquartLatch = 0;
|
|
@@ -1785,7 +1782,7 @@ export class AtomCpu6502 extends Cpu6502 {
|
|
|
1785
1782
|
const bankRoms = this.model.banks || [];
|
|
1786
1783
|
os = "roms/" + os;
|
|
1787
1784
|
console.log(`Loading Atom OS from ${os}`);
|
|
1788
|
-
const data = await
|
|
1785
|
+
const data = await loadData(os);
|
|
1789
1786
|
const len = data.length;
|
|
1790
1787
|
|
|
1791
1788
|
if (len < AtomRomBlockSize || len % AtomRomBlockSize !== 0) {
|
|
@@ -1825,9 +1822,9 @@ export class AtomCpu6502 extends Cpu6502 {
|
|
|
1825
1822
|
// Override loadRom to accept 4KB ROMs
|
|
1826
1823
|
async loadRom(name, offset) {
|
|
1827
1824
|
if (name.indexOf("http") !== 0) name = "roms/" + name;
|
|
1828
|
-
let data = await
|
|
1825
|
+
let data = await loadData(name);
|
|
1829
1826
|
if (/\.zip/i.test(name)) {
|
|
1830
|
-
data = (await
|
|
1827
|
+
data = (await unzipRomImage(data)).data;
|
|
1831
1828
|
}
|
|
1832
1829
|
const len = data.length;
|
|
1833
1830
|
if (len !== 16384 && len !== 8192 && len !== 4096) {
|
|
@@ -1838,7 +1835,7 @@ export class AtomCpu6502 extends Cpu6502 {
|
|
|
1838
1835
|
|
|
1839
1836
|
// Atom key layout goes through PPIA, not SysVia
|
|
1840
1837
|
updateKeyLayout() {
|
|
1841
|
-
this.atomppia.setKeyLayout(this.
|
|
1838
|
+
this.atomppia.setKeyLayout(this.keyLayout);
|
|
1842
1839
|
}
|
|
1843
1840
|
|
|
1844
1841
|
snapshotState(options) {
|