jsbeeb 1.1.1 → 1.3.2
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/package.json +25 -12
- package/src/6502.js +51 -41
- package/src/app/app.js +99 -8
- package/src/app/electron.js +47 -5
- package/src/app/preload.js +11 -1
- package/src/config.js +9 -2
- package/src/disc.js +2 -2
- package/src/filestore.js +1 -4
- package/src/gamepad-source.js +1 -1
- package/src/machine-session.js +396 -0
- package/src/main.js +36 -7
- package/src/music5000-worklet.js +1 -0
- package/src/music5000.js +1 -9
- package/src/sth.js +3 -1
- package/src/utils.js +2 -2
- package/src/web/audio-renderer.js +1 -1
- package/src/web/debug.js +2 -2
- package/tests/test-machine.js +82 -1
- package/.editorconfig +0 -15
- package/.git-blame-ignore-revs +0 -3
- package/.github/copilot-instructions.md +0 -94
- package/.github/workflows/claude-issue-triage.yml +0 -105
- package/.github/workflows/claude.yml +0 -63
- package/.github/workflows/release-please.yml +0 -75
- package/.github/workflows/test-and-deploy.yml +0 -86
- package/.gitmodules +0 -6
- package/.husky/pre-commit +0 -1
- package/.idea/codeStyleSettings.xml +0 -9
- package/.idea/codeStyles/Project.xml +0 -62
- package/.idea/codeStyles/codeStyleConfig.xml +0 -5
- package/.idea/compiler.xml +0 -22
- package/.idea/copyright/profiles_settings.xml +0 -3
- package/.idea/encodings.xml +0 -6
- package/.idea/inspectionProfiles/Project_Default.xml +0 -7
- package/.idea/jsLibraryMappings.xml +0 -6
- package/.idea/jsLinters/jshint.xml +0 -85
- package/.idea/jsLinters/jslint.xml +0 -15
- package/.idea/jsbeeb.iml +0 -11
- package/.idea/misc.xml +0 -6
- package/.idea/modules.xml +0 -8
- package/.idea/prettier.xml +0 -7
- package/.idea/runConfigurations/Debug.xml +0 -5
- package/.idea/scopes/scope_settings.xml +0 -5
- package/.idea/vcs.xml +0 -8
- package/.prettierignore +0 -4
- package/.prettierrc.json +0 -1
- package/.release-please-manifest.json +0 -3
- package/.vscode/launch.json +0 -14
- package/.vscode/settings.json +0 -6
- package/CHANGELOG.md +0 -32
- package/CLAUDE.md +0 -136
- package/Dockerfile +0 -22
- package/Makefile +0 -30
- package/docker/nginx-default.conf +0 -10
- package/docs/pal-comb-filter-research.md +0 -129
- package/docs/pal-simulation-design.md +0 -368
- package/eslint.config.js +0 -35
- package/index.html +0 -954
- package/jsconfig.json +0 -10
- package/public/discs/README.Irq-Timing +0 -3
- package/public/discs/README.bcdtest +0 -5
- package/public/discs/README.elite +0 -6
- package/public/discs/README.eng_test +0 -3
- package/public/discs/README.protection +0 -7
- package/public/favicon.ico +0 -0
- package/public/images/botbar.png +0 -0
- package/public/images/cub-monitor.png +0 -0
- package/public/images/jsbeeb-example.png +0 -0
- package/public/images/placeholder.png +0 -0
- package/public/images/red-off-16.png +0 -0
- package/public/images/red-on-16.png +0 -0
- package/public/images/sb/CD-left.jpg +0 -0
- package/public/images/sb/CD-right.jpg +0 -0
- package/public/images/sidebar.png +0 -0
- package/public/images/tv.png +0 -0
- package/public/images/yellow-off-16.png +0 -0
- package/public/images/yellow-on-16.png +0 -0
- package/public/jsbeeb-icon.png +0 -0
- package/public/robots.txt +0 -3
- package/public/roms/ADFS1-53.rom +0 -0
- package/public/roms/BASIC.ROM +0 -0
- package/public/roms/README +0 -4
- package/public/roms/a01/BASIC1.rom +0 -0
- package/public/roms/ample.rom +0 -0
- package/public/roms/ats-3.0.rom +0 -0
- package/public/roms/b/DFS-0.9.rom +0 -0
- package/public/roms/b/DFS-1.2.rom +0 -0
- package/public/roms/b1770/dfs1770.rom +0 -0
- package/public/roms/b1770/zADFS.ROM +0 -0
- package/public/roms/bp/dfs.rom +0 -0
- package/public/roms/bp/zADFS.ROM +0 -0
- package/public/roms/bpos.rom +0 -0
- package/public/roms/compact/adfs210.rom +0 -0
- package/public/roms/compact/basic48.rom +0 -0
- package/public/roms/compact/basic486.rom +0 -0
- package/public/roms/compact/os51.rom +0 -0
- package/public/roms/compact/utils.rom +0 -0
- package/public/roms/deos.rom +0 -0
- package/public/roms/master/anfs-4.25.rom +0 -0
- package/public/roms/master/mos.txt +0 -68819
- package/public/roms/master/mos3.20 +0 -0
- package/public/roms/os.rom +0 -0
- package/public/roms/os01.rom +0 -0
- package/public/roms/tube/6502Tube.rom +0 -0
- package/public/roms/tube/ARMeval_100.rom +0 -0
- package/public/roms/tube/BIOS.ROM +0 -0
- package/public/roms/tube/ReCo6502ROM_816 +0 -0
- package/public/roms/tube/Z80_120.rom +0 -0
- package/public/roms/us/USBASIC.rom +0 -0
- package/public/roms/us/USDNFS.rom +0 -0
- package/public/roms/usmos.rom +0 -0
- package/public/sounds/disc525/motor.wav +0 -0
- package/public/sounds/disc525/motoroff.wav +0 -0
- package/public/sounds/disc525/motoron.wav +0 -0
- package/public/sounds/disc525/seek.wav +0 -0
- package/public/sounds/disc525/seek2.wav +0 -0
- package/public/sounds/disc525/seek3.wav +0 -0
- package/public/sounds/disc525/step.wav +0 -0
- package/public/teletext/txt0.dat +0 -0
- package/public/teletext/txt1.dat +0 -0
- package/public/teletext/txt2.dat +0 -0
- package/public/teletext/txt3.dat +0 -0
- package/release-please-config.json +0 -13
- package/run-container.sh +0 -92
- package/tests/integration/RmwX.asm +0 -47
- package/tests/integration/TestInstructionsSource +0 -27
- package/tests/integration/TestTimingsResults +0 -27
- package/tests/integration/TestTimingsSource +0 -61
- package/tests/integration/bcd.js +0 -23
- package/tests/integration/dormann.js +0 -101
- package/tests/integration/dp111timing.js +0 -42
- package/tests/integration/ensure-submodules.js +0 -25
- package/tests/integration/nops.bas +0 -119
- package/tests/integration/nops.js +0 -24
- package/tests/integration/protection.js +0 -26
- package/tests/integration/rmw.js +0 -69
- package/tests/integration/teletext/expected_bug_469.png +0 -0
- package/tests/integration/teletext/expected_flash_0.png +0 -0
- package/tests/integration/teletext/expected_flash_1.png +0 -0
- package/tests/integration/teletext/expected_hoglet_held_char.png +0 -0
- package/tests/integration/teletext/expected_reveal_flash_0.png +0 -0
- package/tests/integration/teletext/expected_reveal_flash_1.png +0 -0
- package/tests/integration/teletext.js +0 -126
- package/tests/integration/timings.js +0 -56
- package/tests/integration/via.js +0 -1125
- package/tests/suite/README.md +0 -7
- package/tests/suite/Test Suite 2.15.txt +0 -373
- package/tests/suite/bin/ start +0 -0
- package/tests/suite/bin/adca +0 -0
- package/tests/suite/bin/adcax +0 -0
- package/tests/suite/bin/adcay +0 -0
- package/tests/suite/bin/adcb +0 -0
- package/tests/suite/bin/adcix +0 -0
- package/tests/suite/bin/adciy +0 -0
- package/tests/suite/bin/adcz +0 -0
- package/tests/suite/bin/adczx +0 -0
- package/tests/suite/bin/alrb +0 -0
- package/tests/suite/bin/ancb +0 -0
- package/tests/suite/bin/anda +0 -0
- package/tests/suite/bin/andax +0 -0
- package/tests/suite/bin/anday +0 -0
- package/tests/suite/bin/andb +0 -0
- package/tests/suite/bin/andix +0 -0
- package/tests/suite/bin/andiy +0 -0
- package/tests/suite/bin/andz +0 -0
- package/tests/suite/bin/andzx +0 -0
- package/tests/suite/bin/aneb +0 -0
- package/tests/suite/bin/arrb +0 -0
- package/tests/suite/bin/asla +0 -0
- package/tests/suite/bin/aslax +0 -0
- package/tests/suite/bin/asln +0 -0
- package/tests/suite/bin/aslz +0 -0
- package/tests/suite/bin/aslzx +0 -0
- package/tests/suite/bin/asoa +0 -0
- package/tests/suite/bin/asoax +0 -0
- package/tests/suite/bin/asoay +0 -0
- package/tests/suite/bin/asoix +0 -0
- package/tests/suite/bin/asoiy +0 -0
- package/tests/suite/bin/asoz +0 -0
- package/tests/suite/bin/asozx +0 -0
- package/tests/suite/bin/axsa +0 -0
- package/tests/suite/bin/axsix +0 -0
- package/tests/suite/bin/axsz +0 -0
- package/tests/suite/bin/axszy +0 -0
- package/tests/suite/bin/bccr +0 -0
- package/tests/suite/bin/bcsr +0 -0
- package/tests/suite/bin/beqr +0 -0
- package/tests/suite/bin/bita +0 -0
- package/tests/suite/bin/bitz +0 -0
- package/tests/suite/bin/bmir +0 -0
- package/tests/suite/bin/bner +0 -0
- package/tests/suite/bin/bplr +0 -0
- package/tests/suite/bin/branchwrap +0 -0
- package/tests/suite/bin/brkn +0 -0
- package/tests/suite/bin/bvcr +0 -0
- package/tests/suite/bin/bvsr +0 -0
- package/tests/suite/bin/cia1pb6 +0 -0
- package/tests/suite/bin/cia1pb7 +0 -0
- package/tests/suite/bin/cia1ta +0 -0
- package/tests/suite/bin/cia1tab +0 -0
- package/tests/suite/bin/cia1tb +0 -0
- package/tests/suite/bin/cia1tb123 +0 -0
- package/tests/suite/bin/cia2pb6 +0 -0
- package/tests/suite/bin/cia2pb7 +0 -0
- package/tests/suite/bin/cia2ta +0 -0
- package/tests/suite/bin/cia2tb +0 -0
- package/tests/suite/bin/cia2tb123 +0 -0
- package/tests/suite/bin/clcn +0 -0
- package/tests/suite/bin/cldn +0 -0
- package/tests/suite/bin/clin +0 -0
- package/tests/suite/bin/clvn +0 -0
- package/tests/suite/bin/cmpa +0 -0
- package/tests/suite/bin/cmpax +0 -0
- package/tests/suite/bin/cmpay +0 -0
- package/tests/suite/bin/cmpb +0 -0
- package/tests/suite/bin/cmpix +0 -0
- package/tests/suite/bin/cmpiy +0 -0
- package/tests/suite/bin/cmpz +0 -0
- package/tests/suite/bin/cmpzx +0 -0
- package/tests/suite/bin/cntdef +0 -0
- package/tests/suite/bin/cnto2 +0 -0
- package/tests/suite/bin/cpuport +0 -0
- package/tests/suite/bin/cputiming +0 -0
- package/tests/suite/bin/cpxa +0 -0
- package/tests/suite/bin/cpxb +0 -0
- package/tests/suite/bin/cpxz +0 -0
- package/tests/suite/bin/cpya +0 -0
- package/tests/suite/bin/cpyb +0 -0
- package/tests/suite/bin/cpyz +0 -0
- package/tests/suite/bin/dcma +0 -0
- package/tests/suite/bin/dcmax +0 -0
- package/tests/suite/bin/dcmay +0 -0
- package/tests/suite/bin/dcmix +0 -0
- package/tests/suite/bin/dcmiy +0 -0
- package/tests/suite/bin/dcmz +0 -0
- package/tests/suite/bin/dcmzx +0 -0
- package/tests/suite/bin/deca +0 -0
- package/tests/suite/bin/decax +0 -0
- package/tests/suite/bin/decz +0 -0
- package/tests/suite/bin/deczx +0 -0
- package/tests/suite/bin/dexn +0 -0
- package/tests/suite/bin/deyn +0 -0
- package/tests/suite/bin/eora +0 -0
- package/tests/suite/bin/eorax +0 -0
- package/tests/suite/bin/eoray +0 -0
- package/tests/suite/bin/eorb +0 -0
- package/tests/suite/bin/eorix +0 -0
- package/tests/suite/bin/eoriy +0 -0
- package/tests/suite/bin/eorz +0 -0
- package/tests/suite/bin/eorzx +0 -0
- package/tests/suite/bin/finish +0 -0
- package/tests/suite/bin/flipos +0 -0
- package/tests/suite/bin/icr01 +0 -0
- package/tests/suite/bin/imr +0 -0
- package/tests/suite/bin/inca +0 -0
- package/tests/suite/bin/incax +0 -0
- package/tests/suite/bin/incz +0 -0
- package/tests/suite/bin/inczx +0 -0
- package/tests/suite/bin/insa +0 -0
- package/tests/suite/bin/insax +0 -0
- package/tests/suite/bin/insay +0 -0
- package/tests/suite/bin/insix +0 -0
- package/tests/suite/bin/insiy +0 -0
- package/tests/suite/bin/insz +0 -0
- package/tests/suite/bin/inszx +0 -0
- package/tests/suite/bin/inxn +0 -0
- package/tests/suite/bin/inyn +0 -0
- package/tests/suite/bin/irq +0 -0
- package/tests/suite/bin/jmpi +0 -0
- package/tests/suite/bin/jmpw +0 -0
- package/tests/suite/bin/jsrw +0 -0
- package/tests/suite/bin/lasay +0 -0
- package/tests/suite/bin/laxa +0 -0
- package/tests/suite/bin/laxay +0 -0
- package/tests/suite/bin/laxix +0 -0
- package/tests/suite/bin/laxiy +0 -0
- package/tests/suite/bin/laxz +0 -0
- package/tests/suite/bin/laxzy +0 -0
- package/tests/suite/bin/ldaa +0 -0
- package/tests/suite/bin/ldaax +0 -0
- package/tests/suite/bin/ldaay +0 -0
- package/tests/suite/bin/ldab +0 -0
- package/tests/suite/bin/ldaix +0 -0
- package/tests/suite/bin/ldaiy +0 -0
- package/tests/suite/bin/ldaz +0 -0
- package/tests/suite/bin/ldazx +0 -0
- package/tests/suite/bin/ldxa +0 -0
- package/tests/suite/bin/ldxay +0 -0
- package/tests/suite/bin/ldxb +0 -0
- package/tests/suite/bin/ldxz +0 -0
- package/tests/suite/bin/ldxzy +0 -0
- package/tests/suite/bin/ldya +0 -0
- package/tests/suite/bin/ldyax +0 -0
- package/tests/suite/bin/ldyb +0 -0
- package/tests/suite/bin/ldyz +0 -0
- package/tests/suite/bin/ldyzx +0 -0
- package/tests/suite/bin/loadth +0 -0
- package/tests/suite/bin/lsea +0 -0
- package/tests/suite/bin/lseax +0 -0
- package/tests/suite/bin/lseay +0 -0
- package/tests/suite/bin/lseix +0 -0
- package/tests/suite/bin/lseiy +0 -0
- package/tests/suite/bin/lsez +0 -0
- package/tests/suite/bin/lsezx +0 -0
- package/tests/suite/bin/lsra +0 -0
- package/tests/suite/bin/lsrax +0 -0
- package/tests/suite/bin/lsrn +0 -0
- package/tests/suite/bin/lsrz +0 -0
- package/tests/suite/bin/lsrzx +0 -0
- package/tests/suite/bin/lxab +0 -0
- package/tests/suite/bin/mmu +0 -0
- package/tests/suite/bin/mmufetch +0 -0
- package/tests/suite/bin/nmi +0 -0
- package/tests/suite/bin/nopa +0 -0
- package/tests/suite/bin/nopax +0 -0
- package/tests/suite/bin/nopb +0 -0
- package/tests/suite/bin/nopn +0 -0
- package/tests/suite/bin/nopz +0 -0
- package/tests/suite/bin/nopzx +0 -0
- package/tests/suite/bin/oneshot +0 -0
- package/tests/suite/bin/oraa +0 -0
- package/tests/suite/bin/oraax +0 -0
- package/tests/suite/bin/oraay +0 -0
- package/tests/suite/bin/orab +0 -0
- package/tests/suite/bin/oraix +0 -0
- package/tests/suite/bin/oraiy +0 -0
- package/tests/suite/bin/oraz +0 -0
- package/tests/suite/bin/orazx +0 -0
- package/tests/suite/bin/phan +0 -0
- package/tests/suite/bin/phpn +0 -0
- package/tests/suite/bin/plan +0 -0
- package/tests/suite/bin/plpn +0 -0
- package/tests/suite/bin/rlaa +0 -0
- package/tests/suite/bin/rlaax +0 -0
- package/tests/suite/bin/rlaay +0 -0
- package/tests/suite/bin/rlaix +0 -0
- package/tests/suite/bin/rlaiy +0 -0
- package/tests/suite/bin/rlaz +0 -0
- package/tests/suite/bin/rlazx +0 -0
- package/tests/suite/bin/rola +0 -0
- package/tests/suite/bin/rolax +0 -0
- package/tests/suite/bin/roln +0 -0
- package/tests/suite/bin/rolz +0 -0
- package/tests/suite/bin/rolzx +0 -0
- package/tests/suite/bin/rora +0 -0
- package/tests/suite/bin/rorax +0 -0
- package/tests/suite/bin/rorn +0 -0
- package/tests/suite/bin/rorz +0 -0
- package/tests/suite/bin/rorzx +0 -0
- package/tests/suite/bin/rraa +0 -0
- package/tests/suite/bin/rraax +0 -0
- package/tests/suite/bin/rraay +0 -0
- package/tests/suite/bin/rraix +0 -0
- package/tests/suite/bin/rraiy +0 -0
- package/tests/suite/bin/rraz +0 -0
- package/tests/suite/bin/rrazx +0 -0
- package/tests/suite/bin/rtin +0 -0
- package/tests/suite/bin/rtsn +0 -0
- package/tests/suite/bin/sbca +0 -0
- package/tests/suite/bin/sbcax +0 -0
- package/tests/suite/bin/sbcay +0 -0
- package/tests/suite/bin/sbcb +0 -0
- package/tests/suite/bin/sbcb(eb) +0 -0
- package/tests/suite/bin/sbcix +0 -0
- package/tests/suite/bin/sbciy +0 -0
- package/tests/suite/bin/sbcz +0 -0
- package/tests/suite/bin/sbczx +0 -0
- package/tests/suite/bin/sbxb +0 -0
- package/tests/suite/bin/secn +0 -0
- package/tests/suite/bin/sedn +0 -0
- package/tests/suite/bin/sein +0 -0
- package/tests/suite/bin/shaay +0 -0
- package/tests/suite/bin/shaiy +0 -0
- package/tests/suite/bin/shsay +0 -0
- package/tests/suite/bin/shxay +0 -0
- package/tests/suite/bin/shyax +0 -0
- package/tests/suite/bin/staa +0 -0
- package/tests/suite/bin/staax +0 -0
- package/tests/suite/bin/staay +0 -0
- package/tests/suite/bin/staix +0 -0
- package/tests/suite/bin/staiy +0 -0
- package/tests/suite/bin/staz +0 -0
- package/tests/suite/bin/stazx +0 -0
- package/tests/suite/bin/stxa +0 -0
- package/tests/suite/bin/stxz +0 -0
- package/tests/suite/bin/stxzy +0 -0
- package/tests/suite/bin/stya +0 -0
- package/tests/suite/bin/styz +0 -0
- package/tests/suite/bin/styzx +0 -0
- package/tests/suite/bin/taxn +0 -0
- package/tests/suite/bin/tayn +0 -0
- package/tests/suite/bin/trap1 +0 -0
- package/tests/suite/bin/trap10 +0 -0
- package/tests/suite/bin/trap11 +0 -0
- package/tests/suite/bin/trap12 +0 -0
- package/tests/suite/bin/trap13 +0 -0
- package/tests/suite/bin/trap14 +0 -0
- package/tests/suite/bin/trap15 +0 -0
- package/tests/suite/bin/trap16 +0 -0
- package/tests/suite/bin/trap17 +0 -0
- package/tests/suite/bin/trap2 +0 -0
- package/tests/suite/bin/trap3 +0 -0
- package/tests/suite/bin/trap4 +0 -0
- package/tests/suite/bin/trap5 +0 -0
- package/tests/suite/bin/trap6 +0 -0
- package/tests/suite/bin/trap7 +0 -0
- package/tests/suite/bin/trap8 +0 -0
- package/tests/suite/bin/trap9 +0 -0
- package/tests/suite/bin/tsxn +0 -0
- package/tests/suite/bin/txan +0 -0
- package/tests/suite/bin/txsn +0 -0
- package/tests/suite/bin/tyan +0 -0
- package/tests/suite/cbm-hackers-post.html +0 -178
- package/tests/suite/cbm-hackers-post.md +0 -78
- package/tests/test-suite.js +0 -147
- package/tests/test.css +0 -7
- package/tests/unit/gzip/test-1 +0 -0
- package/tests/unit/gzip/test-1.gz +0 -0
- package/tests/unit/gzip/test-2 +0 -0
- package/tests/unit/gzip/test-2.gz +0 -0
- package/tests/unit/gzip/test-3 +0 -0
- package/tests/unit/gzip/test-3.gz +0 -0
- package/tests/unit/gzip/test-4 +0 -0
- package/tests/unit/gzip/test-4.gz +0 -0
- package/tests/unit/test-adc.js +0 -307
- package/tests/unit/test-bcd.js +0 -30
- package/tests/unit/test-cmos.js +0 -266
- package/tests/unit/test-disc-drive.js +0 -85
- package/tests/unit/test-disc-hfe.js +0 -347
- package/tests/unit/test-disc.js +0 -232
- package/tests/unit/test-fifo.js +0 -35
- package/tests/unit/test-gamepad-source.js +0 -67
- package/tests/unit/test-gzip.js +0 -22
- package/tests/unit/test-intel-fdc.js +0 -93
- package/tests/unit/test-keyboard.js +0 -410
- package/tests/unit/test-mouse-joystick-source.js +0 -128
- package/tests/unit/test-scheduler.js +0 -190
- package/tests/unit/test-serial.js +0 -154
- package/tests/unit/test-teletext-adaptor.js +0 -359
- package/tests/unit/test-tokenise.js +0 -65
- package/tests/unit/test-url-params.js +0 -398
- package/tests/unit/test-utils.js +0 -276
- package/tests/unit/test-video.js +0 -498
- package/tests/unit/test-zip.js +0 -56
- package/tests/unit/zip/test-mixed.zip +0 -0
- package/tests/unit/zip/test-rom.zip +0 -0
- package/tests/unit/zip/test-ssd.zip +0 -0
- package/tools/fir-generator.js +0 -80
- package/tools/vite-plugin-fir-shader.js +0 -131
- package/vite.config.js +0 -34
|
@@ -0,0 +1,396 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* MachineSession - wraps jsbeeb's TestMachine with:
|
|
3
|
+
* - real Video framebuffer (so screenshots work)
|
|
4
|
+
* - accumulated text output between calls
|
|
5
|
+
* - clean lifecycle (boot, interact, screenshot, destroy)
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { readFileSync } from "fs";
|
|
9
|
+
import { fileURLToPath } from "url";
|
|
10
|
+
import path from "path";
|
|
11
|
+
import { TestMachine } from "../tests/test-machine.js";
|
|
12
|
+
|
|
13
|
+
// Resolve the jsbeeb package root at module load time using our own location
|
|
14
|
+
// (src/machine-session.js → go up one level). We chdir here during
|
|
15
|
+
// initialise() so jsbeeb's ROM loader ("roms/os.rom" etc.) always resolves
|
|
16
|
+
// correctly, regardless of where the calling process was started from.
|
|
17
|
+
const _jsbeebRoot = path.dirname(path.dirname(fileURLToPath(import.meta.url)));
|
|
18
|
+
import * as fdc from "./fdc.js";
|
|
19
|
+
import { Video } from "./video.js";
|
|
20
|
+
import { findModel } from "./models.js";
|
|
21
|
+
import sharp from "sharp";
|
|
22
|
+
|
|
23
|
+
// BBC framebuffer is 1024×625 pixels
|
|
24
|
+
const FB_WIDTH = 1024;
|
|
25
|
+
const FB_HEIGHT = 625;
|
|
26
|
+
|
|
27
|
+
export class MachineSession {
|
|
28
|
+
/**
|
|
29
|
+
* @param {string} modelName - e.g. "B-DFS1.2", "Master"
|
|
30
|
+
*/
|
|
31
|
+
constructor(modelName = "B-DFS1.2") {
|
|
32
|
+
this.modelName = modelName;
|
|
33
|
+
|
|
34
|
+
// Raw RGBA framebuffer — the Video chip renders into _fb32 (cleared each frame).
|
|
35
|
+
// _completeFb8 is a snapshot taken at paint time (the equivalent of the browser canvas)
|
|
36
|
+
// and is what screenshot() reads from — always a complete frame, never mid-render.
|
|
37
|
+
this._fb8 = new Uint8Array(FB_WIDTH * FB_HEIGHT * 4);
|
|
38
|
+
this._fb32 = new Uint32Array(this._fb8.buffer);
|
|
39
|
+
this._completeFb8 = new Uint8Array(FB_WIDTH * FB_HEIGHT * 4);
|
|
40
|
+
this._lastPaint = { minx: 0, miny: 0, maxx: FB_WIDTH, maxy: FB_HEIGHT };
|
|
41
|
+
this._frameDirty = false;
|
|
42
|
+
|
|
43
|
+
// Create a real Video instance so we get pixel output
|
|
44
|
+
const modelObj = findModel(modelName);
|
|
45
|
+
this._video = new Video(modelObj.isMaster, this._fb32, (minx, miny, maxx, maxy) => {
|
|
46
|
+
this._lastPaint = { minx, miny, maxx, maxy };
|
|
47
|
+
this._frameDirty = true;
|
|
48
|
+
// Snapshot the complete frame now, before clearPaintBuffer() wipes _fb32.
|
|
49
|
+
// This mirrors what the browser does: paint_ext fires → canvas updated → fb32 cleared.
|
|
50
|
+
this._completeFb8.set(this._fb8);
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
// TestMachine forwards opts.video to fake6502, which uses it instead of FakeVideo
|
|
54
|
+
this._machine = new TestMachine(modelName, { video: this._video });
|
|
55
|
+
|
|
56
|
+
// Accumulated VDU text output — drained by callers
|
|
57
|
+
this._pendingOutput = [];
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/** Load ROMs and hardware — call once before anything else */
|
|
61
|
+
async initialise() {
|
|
62
|
+
// jsbeeb's ROM loader uses paths relative to cwd ("roms/os.rom" etc.).
|
|
63
|
+
// Chdir to the package root so it works regardless of where the
|
|
64
|
+
// calling process was launched from.
|
|
65
|
+
process.chdir(_jsbeebRoot);
|
|
66
|
+
await this._machine.initialise();
|
|
67
|
+
this._installCaptureHook();
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Boot the machine (run until the BASIC prompt).
|
|
72
|
+
* Returns captured boot-screen text (the OS banner etc.).
|
|
73
|
+
*/
|
|
74
|
+
async boot(timeoutSecs = 30) {
|
|
75
|
+
await this._machine.runUntilInput(timeoutSecs);
|
|
76
|
+
return this.drainOutput();
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* Install the VDU character-output capture hook.
|
|
81
|
+
*
|
|
82
|
+
* WRCHV discovery: RAM at 0x20E/0x20F initialises to 0xFFFF before the
|
|
83
|
+
* OS runs. We read directly from cpu.ramRomOs (two array lookups — no
|
|
84
|
+
* readmem() dispatch overhead) on every instruction, waiting for the
|
|
85
|
+
* value to change from its initial 0xFFFF. Once the OS installs a real
|
|
86
|
+
* handler we use that address for the lifetime of the session. Programs
|
|
87
|
+
* that later install a custom VDU driver are handled seamlessly because
|
|
88
|
+
* we always re-read from the live memory.
|
|
89
|
+
*
|
|
90
|
+
* Text elements: { x, y, text, foreground, background, mode }
|
|
91
|
+
* Screenshots (via the real Video chip) are the right tool for anything
|
|
92
|
+
* visual; this capture is a lightweight aid for text-mode output only.
|
|
93
|
+
*/
|
|
94
|
+
_installCaptureHook() {
|
|
95
|
+
const cpu = this._machine.processor;
|
|
96
|
+
const ram = cpu.ramRomOs; // direct Uint8Array — no dispatch overhead
|
|
97
|
+
const initialWrchv = ram[0x20e] | (ram[0x20f] << 8); // 0xFFFF pre-boot
|
|
98
|
+
|
|
99
|
+
const attributes = { x: 0, y: 0, text: "", foreground: 7, background: 0, mode: 7 };
|
|
100
|
+
let currentText = "";
|
|
101
|
+
let params = [];
|
|
102
|
+
let nextN = 0;
|
|
103
|
+
let vduProc = null;
|
|
104
|
+
|
|
105
|
+
const onElement = (elem) => this._pendingOutput.push({ ...elem });
|
|
106
|
+
|
|
107
|
+
function flush() {
|
|
108
|
+
if (currentText.length) {
|
|
109
|
+
attributes.text = currentText;
|
|
110
|
+
onElement({ ...attributes });
|
|
111
|
+
attributes.x += currentText.length;
|
|
112
|
+
}
|
|
113
|
+
currentText = "";
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
function onChar(c) {
|
|
117
|
+
if (nextN) {
|
|
118
|
+
params.push(c);
|
|
119
|
+
if (--nextN === 0) {
|
|
120
|
+
if (vduProc) vduProc(params);
|
|
121
|
+
params = [];
|
|
122
|
+
vduProc = null;
|
|
123
|
+
}
|
|
124
|
+
return;
|
|
125
|
+
}
|
|
126
|
+
switch (c) {
|
|
127
|
+
case 1: // next char to printer only
|
|
128
|
+
nextN = 1;
|
|
129
|
+
break;
|
|
130
|
+
case 10: // LF
|
|
131
|
+
flush();
|
|
132
|
+
attributes.y++;
|
|
133
|
+
break;
|
|
134
|
+
case 12: // CLS
|
|
135
|
+
flush();
|
|
136
|
+
attributes.x = 0;
|
|
137
|
+
attributes.y = 0;
|
|
138
|
+
break;
|
|
139
|
+
case 13: // CR
|
|
140
|
+
flush();
|
|
141
|
+
attributes.x = 0;
|
|
142
|
+
break;
|
|
143
|
+
case 17: // COLOUR n
|
|
144
|
+
nextN = 1;
|
|
145
|
+
vduProc = (p) => {
|
|
146
|
+
if (p[0] & 0x80) attributes.background = p[0] & 0xf;
|
|
147
|
+
else attributes.foreground = p[0] & 0xf;
|
|
148
|
+
};
|
|
149
|
+
break;
|
|
150
|
+
case 18: // GCOL
|
|
151
|
+
nextN = 2;
|
|
152
|
+
break;
|
|
153
|
+
case 19: // define logical colour
|
|
154
|
+
nextN = 5;
|
|
155
|
+
break;
|
|
156
|
+
case 22: // MODE n
|
|
157
|
+
nextN = 1;
|
|
158
|
+
vduProc = (p) => {
|
|
159
|
+
flush();
|
|
160
|
+
attributes.mode = p[0];
|
|
161
|
+
attributes.x = 0;
|
|
162
|
+
attributes.y = 0;
|
|
163
|
+
attributes.foreground = 7;
|
|
164
|
+
attributes.background = 0;
|
|
165
|
+
};
|
|
166
|
+
break;
|
|
167
|
+
case 25: // PLOT
|
|
168
|
+
nextN = 5;
|
|
169
|
+
break;
|
|
170
|
+
case 28: // define text window
|
|
171
|
+
nextN = 4;
|
|
172
|
+
break;
|
|
173
|
+
case 29: // define graphics origin
|
|
174
|
+
nextN = 4;
|
|
175
|
+
break;
|
|
176
|
+
case 31: // TAB(x,y)
|
|
177
|
+
nextN = 2;
|
|
178
|
+
vduProc = (p) => {
|
|
179
|
+
flush();
|
|
180
|
+
attributes.x = p[0];
|
|
181
|
+
attributes.y = p[1];
|
|
182
|
+
};
|
|
183
|
+
break;
|
|
184
|
+
default:
|
|
185
|
+
if (c >= 32 && c < 0x7f) {
|
|
186
|
+
currentText += String.fromCharCode(c);
|
|
187
|
+
} else {
|
|
188
|
+
flush();
|
|
189
|
+
}
|
|
190
|
+
break;
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
cpu.debugInstruction.add((addr) => {
|
|
195
|
+
// Two direct array reads — no function-call dispatch overhead.
|
|
196
|
+
// Once the OS sets WRCHV (it changes from 0xFFFF), we start
|
|
197
|
+
// capturing. Programs that install a custom VDU driver mid-run
|
|
198
|
+
// are handled transparently because we re-read on every call.
|
|
199
|
+
const wrchv = ram[0x20e] | (ram[0x20f] << 8);
|
|
200
|
+
if (wrchv !== initialWrchv && addr === wrchv) {
|
|
201
|
+
onChar(cpu.a);
|
|
202
|
+
}
|
|
203
|
+
return false;
|
|
204
|
+
});
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
/** Tokenise BBC BASIC source and write it into PAGE */
|
|
208
|
+
async loadBasic(source) {
|
|
209
|
+
await this._machine.loadBasic(source);
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
/**
|
|
213
|
+
* Simulate keypresses. Note: each character needs a short run to be
|
|
214
|
+
* picked up by the OS, so this is internally async and slow-ish (by
|
|
215
|
+
* emulated-time).
|
|
216
|
+
*/
|
|
217
|
+
async type(text) {
|
|
218
|
+
await this._machine.type(text);
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
/**
|
|
222
|
+
* Run the emulator until the OS is waiting at the keyboard prompt, or
|
|
223
|
+
* until timeoutSecs of emulated time elapses. Returns captured output.
|
|
224
|
+
*
|
|
225
|
+
* @param {number} [timeoutSecs=60]
|
|
226
|
+
* @param {Object} [opts]
|
|
227
|
+
* @param {boolean} [opts.clear=true] - Whether to clear the output buffer after returning it.
|
|
228
|
+
*/
|
|
229
|
+
async runUntilPrompt(timeoutSecs = 60, { clear = true } = {}) {
|
|
230
|
+
await this._machine.runUntilInput(timeoutSecs);
|
|
231
|
+
return this.drainOutput({ clear });
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
/**
|
|
235
|
+
* Run for an exact number of emulated CPU cycles.
|
|
236
|
+
* Useful for timing-sensitive code.
|
|
237
|
+
*/
|
|
238
|
+
async runFor(cycles) {
|
|
239
|
+
await this._machine.runFor(cycles);
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
/**
|
|
243
|
+
* Run until PC reaches targetAddr (like a breakpoint), or timeout.
|
|
244
|
+
*/
|
|
245
|
+
async runUntilAddress(addr, timeoutSecs = 30) {
|
|
246
|
+
await this._machine.runUntilAddress(addr, timeoutSecs);
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
/**
|
|
250
|
+
* Load a disc image (absolute or relative path to an .ssd or .dsd file).
|
|
251
|
+
*
|
|
252
|
+
* We read the file ourselves rather than delegating to TestMachine.loadDisc,
|
|
253
|
+
* which goes through utils.loadData and mangles absolute paths by prepending "./".
|
|
254
|
+
*/
|
|
255
|
+
loadDisc(imagePath) {
|
|
256
|
+
const data = new Uint8Array(readFileSync(imagePath));
|
|
257
|
+
this._machine.processor.fdc.loadDisc(0, fdc.discFor(this._machine.processor.fdc, imagePath, data));
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
/**
|
|
261
|
+
* Return all VDU text elements captured so far.
|
|
262
|
+
*
|
|
263
|
+
* @param {Object} [opts]
|
|
264
|
+
* @param {boolean} [opts.clear=true] - If true (default), clear the buffer
|
|
265
|
+
* after returning it. Pass false to peek without consuming — the same
|
|
266
|
+
* elements will be returned again on the next call.
|
|
267
|
+
*
|
|
268
|
+
* Each element: { x, y, text, foreground, background, mode }
|
|
269
|
+
* Also includes a flat `screenText` reconstruction.
|
|
270
|
+
*/
|
|
271
|
+
drainOutput({ clear = true } = {}) {
|
|
272
|
+
const elements = clear ? this._pendingOutput.splice(0) : [...this._pendingOutput];
|
|
273
|
+
return {
|
|
274
|
+
elements,
|
|
275
|
+
screenText: reconstructScreenText(elements),
|
|
276
|
+
};
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
/** Read `length` bytes from emulator memory starting at `address` */
|
|
280
|
+
readMemory(address, length = 16) {
|
|
281
|
+
const bytes = [];
|
|
282
|
+
for (let i = 0; i < length; i++) {
|
|
283
|
+
bytes.push(this._machine.readbyte(address + i));
|
|
284
|
+
}
|
|
285
|
+
return bytes;
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
/** Write an array of byte values into emulator memory at `address` */
|
|
289
|
+
writeMemory(address, bytes) {
|
|
290
|
+
for (let i = 0; i < bytes.length; i++) {
|
|
291
|
+
this._machine.writebyte(address + i, bytes[i]);
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
/** Read the current 6502 CPU registers */
|
|
296
|
+
registers() {
|
|
297
|
+
const cpu = this._machine.processor;
|
|
298
|
+
return {
|
|
299
|
+
pc: cpu.pc,
|
|
300
|
+
a: cpu.a,
|
|
301
|
+
x: cpu.x,
|
|
302
|
+
y: cpu.y,
|
|
303
|
+
s: cpu.s, // stack pointer
|
|
304
|
+
p: cpu.p, // processor status
|
|
305
|
+
pcHex: `0x${cpu.pc.toString(16).toUpperCase().padStart(4, "0")}`,
|
|
306
|
+
aHex: `0x${cpu.a.toString(16).toUpperCase().padStart(2, "0")}`,
|
|
307
|
+
xHex: `0x${cpu.x.toString(16).toUpperCase().padStart(2, "0")}`,
|
|
308
|
+
yHex: `0x${cpu.y.toString(16).toUpperCase().padStart(2, "0")}`,
|
|
309
|
+
};
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
/**
|
|
313
|
+
* Capture the current screen as a PNG.
|
|
314
|
+
* Returns a Buffer containing a 1024×625 PNG (the full emulated display,
|
|
315
|
+
* including borders — matches what the browser renders).
|
|
316
|
+
*
|
|
317
|
+
* The active display area is roughly:
|
|
318
|
+
* x: leftBorder .. 1024-rightBorder
|
|
319
|
+
* y: topBorder .. 625-bottomBorder
|
|
320
|
+
*/
|
|
321
|
+
async screenshot() {
|
|
322
|
+
// Read from _completeFb8 — the last fully-painted frame snapshotted in paint_ext.
|
|
323
|
+
// _fb8/_fb32 is the live render buffer (cleared and partially refilled each frame).
|
|
324
|
+
return sharp(Buffer.from(this._completeFb8.buffer), {
|
|
325
|
+
raw: { width: FB_WIDTH, height: FB_HEIGHT, channels: 4 },
|
|
326
|
+
})
|
|
327
|
+
.png()
|
|
328
|
+
.toBuffer();
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
/**
|
|
332
|
+
* Capture only the active display area (no overscan borders), scaled to
|
|
333
|
+
* a more sensible pixel density. Returns a PNG Buffer.
|
|
334
|
+
*
|
|
335
|
+
* @param {Object} [opts]
|
|
336
|
+
* @param {number} [opts.scale=2] - integer scale factor
|
|
337
|
+
*/
|
|
338
|
+
async screenshotActive(opts = {}) {
|
|
339
|
+
const scale = opts.scale ?? 2;
|
|
340
|
+
const v = this._video;
|
|
341
|
+
const left = v.leftBorder;
|
|
342
|
+
const top = v.topBorder;
|
|
343
|
+
const right = v.rightBorder;
|
|
344
|
+
const bottom = v.bottomBorder;
|
|
345
|
+
const w = FB_WIDTH - left - right;
|
|
346
|
+
const h = FB_HEIGHT - top - bottom;
|
|
347
|
+
|
|
348
|
+
return sharp(Buffer.from(this._completeFb8.buffer), {
|
|
349
|
+
raw: { width: FB_WIDTH, height: FB_HEIGHT, channels: 4 },
|
|
350
|
+
})
|
|
351
|
+
.extract({ left, top, width: w, height: h })
|
|
352
|
+
.resize(w * scale, h * scale, { kernel: "nearest" }) // nearest-neighbour keeps pixels crisp
|
|
353
|
+
.png()
|
|
354
|
+
.toBuffer();
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
/** Free any resources (currently just clears the framebuffer) */
|
|
358
|
+
destroy() {
|
|
359
|
+
this._fb8.fill(0);
|
|
360
|
+
this._pendingOutput = [];
|
|
361
|
+
}
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
// ---------------------------------------------------------------------------
|
|
365
|
+
// Helpers
|
|
366
|
+
// ---------------------------------------------------------------------------
|
|
367
|
+
|
|
368
|
+
/**
|
|
369
|
+
* Reconstruct a flat text representation from the captured VDU elements.
|
|
370
|
+
* Builds a 2D grid of characters and returns it as a newline-separated string.
|
|
371
|
+
*/
|
|
372
|
+
function reconstructScreenText(elements) {
|
|
373
|
+
if (elements.length === 0) return "";
|
|
374
|
+
|
|
375
|
+
// Find bounds
|
|
376
|
+
let maxX = 0;
|
|
377
|
+
let maxY = 0;
|
|
378
|
+
for (const el of elements) {
|
|
379
|
+
const endX = el.x + el.text.length;
|
|
380
|
+
if (endX > maxX) maxX = endX;
|
|
381
|
+
if (el.y > maxY) maxY = el.y;
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
// Fill grid
|
|
385
|
+
const rows = Array.from({ length: maxY + 1 }, () => Array(maxX + 1).fill(" "));
|
|
386
|
+
for (const el of elements) {
|
|
387
|
+
for (let i = 0; i < el.text.length; i++) {
|
|
388
|
+
const col = el.x + i;
|
|
389
|
+
if (col < rows[el.y].length) {
|
|
390
|
+
rows[el.y][col] = el.text[i];
|
|
391
|
+
}
|
|
392
|
+
}
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
return rows.map((r) => r.join("").trimEnd()).join("\n");
|
|
396
|
+
}
|
package/src/main.js
CHANGED
|
@@ -129,7 +129,7 @@ const emuKeyHandlers = {};
|
|
|
129
129
|
let cpuMultiplier = 1;
|
|
130
130
|
let fastAsPossible = false;
|
|
131
131
|
let fastTape = false;
|
|
132
|
-
let noSeek
|
|
132
|
+
let noSeek;
|
|
133
133
|
let audioFilterFreq = 7000;
|
|
134
134
|
let audioFilterQ = 5;
|
|
135
135
|
let stationId = 101;
|
|
@@ -287,7 +287,7 @@ function sbBind(div, url, onload) {
|
|
|
287
287
|
const img = div.find("img");
|
|
288
288
|
img.hide();
|
|
289
289
|
if (!url) return;
|
|
290
|
-
img.attr("src", url).
|
|
290
|
+
img.attr("src", url).on("load", function () {
|
|
291
291
|
onload(div, img);
|
|
292
292
|
img.show();
|
|
293
293
|
});
|
|
@@ -348,7 +348,7 @@ function swapCanvas(newFilterClass) {
|
|
|
348
348
|
};
|
|
349
349
|
canvas = newCanvas;
|
|
350
350
|
displayModeFilter = newFilterClass;
|
|
351
|
-
window.setTimeout(() => window.
|
|
351
|
+
window.setTimeout(() => window.dispatchEvent(new Event("resize")), 1);
|
|
352
352
|
}
|
|
353
353
|
|
|
354
354
|
let canvas = createCanvasForFilter(displayModeFilter);
|
|
@@ -766,6 +766,13 @@ function setDisc1Image(name) {
|
|
|
766
766
|
delete parsedQuery.disc;
|
|
767
767
|
parsedQuery.disc1 = name;
|
|
768
768
|
updateUrl();
|
|
769
|
+
config.emit("media-changed", { disc1: name });
|
|
770
|
+
}
|
|
771
|
+
|
|
772
|
+
function setTapeImage(name) {
|
|
773
|
+
parsedQuery.tape = name;
|
|
774
|
+
updateUrl();
|
|
775
|
+
config.emit("media-changed", { tape: name });
|
|
769
776
|
}
|
|
770
777
|
|
|
771
778
|
function sthClearList() {
|
|
@@ -804,8 +811,7 @@ async function discSthClick(item) {
|
|
|
804
811
|
|
|
805
812
|
async function tapeSthClick(item) {
|
|
806
813
|
utils.noteEvent("sth", "clickTape", item);
|
|
807
|
-
|
|
808
|
-
updateUrl();
|
|
814
|
+
setTapeImage("sth:" + item);
|
|
809
815
|
|
|
810
816
|
popupLoading("Loading " + item);
|
|
811
817
|
try {
|
|
@@ -1027,7 +1033,8 @@ async function loadTapeImage(tapeImage) {
|
|
|
1027
1033
|
}
|
|
1028
1034
|
|
|
1029
1035
|
case "http":
|
|
1030
|
-
case "https":
|
|
1036
|
+
case "https":
|
|
1037
|
+
case "file": {
|
|
1031
1038
|
const asUrl = `${schema}://${tapeImage}`;
|
|
1032
1039
|
// url may end in query params etc, which can upset file handling
|
|
1033
1040
|
tapeImage = new URL(asUrl).pathname;
|
|
@@ -1750,7 +1757,29 @@ window.m7dump = function () {
|
|
|
1750
1757
|
};
|
|
1751
1758
|
|
|
1752
1759
|
// Hooks for electron.
|
|
1753
|
-
electron({
|
|
1760
|
+
electron({
|
|
1761
|
+
loadDiscImage,
|
|
1762
|
+
loadTapeImage,
|
|
1763
|
+
processor,
|
|
1764
|
+
config,
|
|
1765
|
+
modals: {
|
|
1766
|
+
show: (modalId, sthType) => {
|
|
1767
|
+
if (modalId === "sth" && sthType) {
|
|
1768
|
+
if (sthType === "discs") discSth.populate();
|
|
1769
|
+
else if (sthType === "tapes") tapeSth.populate();
|
|
1770
|
+
}
|
|
1771
|
+
const modalEl = document.getElementById(modalId);
|
|
1772
|
+
if (modalEl) {
|
|
1773
|
+
const modal = bootstrap.Modal.getOrCreateInstance(modalEl);
|
|
1774
|
+
modal.show();
|
|
1775
|
+
}
|
|
1776
|
+
},
|
|
1777
|
+
},
|
|
1778
|
+
actions: {
|
|
1779
|
+
"soft-reset": () => processor.reset(false),
|
|
1780
|
+
"hard-reset": () => processor.reset(true),
|
|
1781
|
+
},
|
|
1782
|
+
});
|
|
1754
1783
|
|
|
1755
1784
|
// Display version in About dialog
|
|
1756
1785
|
const versionElement = document.getElementById("jsbeeb-version");
|
package/src/music5000-worklet.js
CHANGED
package/src/music5000.js
CHANGED
|
@@ -112,15 +112,7 @@ export class Music5000 {
|
|
|
112
112
|
}
|
|
113
113
|
|
|
114
114
|
polltime(cycles) {
|
|
115
|
-
let c4d,
|
|
116
|
-
freq,
|
|
117
|
-
offset,
|
|
118
|
-
wavetable,
|
|
119
|
-
amplitude,
|
|
120
|
-
control,
|
|
121
|
-
data,
|
|
122
|
-
sign,
|
|
123
|
-
pos = 0 >>> 0;
|
|
115
|
+
let c4d, freq, offset, wavetable, amplitude, control, data, sign, pos;
|
|
124
116
|
|
|
125
117
|
// Convert 2MHz 6502 cycles to 6MHz Music5000 cycles
|
|
126
118
|
this.cycleCount += cycles * 3;
|
package/src/sth.js
CHANGED
|
@@ -23,7 +23,9 @@ async function _fetchAndParseCatalog(url) {
|
|
|
23
23
|
|
|
24
24
|
export class StairwayToHell {
|
|
25
25
|
constructor(onStart, onCat, onError, tape) {
|
|
26
|
-
|
|
26
|
+
// Use https explicitly - document.location.protocol is 'file:' in Electron
|
|
27
|
+
const protocol = document.location.protocol === "file:" ? "https:" : document.location.protocol;
|
|
28
|
+
this._baseUrl = `${protocol}//${sthArchive}/${tape ? "tape" : "disk"}images/`;
|
|
27
29
|
this._catalog = [];
|
|
28
30
|
this._onStart = onStart;
|
|
29
31
|
this._onCat = onCat;
|
package/src/utils.js
CHANGED
|
@@ -890,7 +890,7 @@ export function ungzip(data) {
|
|
|
890
890
|
try {
|
|
891
891
|
return pakoUngzip(data);
|
|
892
892
|
} catch (e) {
|
|
893
|
-
throw new Error("Unable to ungzip: " + e.message);
|
|
893
|
+
throw new Error("Unable to ungzip: " + e.message, { cause: e });
|
|
894
894
|
}
|
|
895
895
|
}
|
|
896
896
|
|
|
@@ -1000,7 +1000,7 @@ function unzipImage(data, knownExtensions) {
|
|
|
1000
1000
|
try {
|
|
1001
1001
|
files = unzipSync(data instanceof Uint8Array ? data : new Uint8Array(data));
|
|
1002
1002
|
} catch (e) {
|
|
1003
|
-
throw new Error("Error unzipping " + e.message);
|
|
1003
|
+
throw new Error("Error unzipping " + e.message, { cause: e });
|
|
1004
1004
|
}
|
|
1005
1005
|
|
|
1006
1006
|
let uncompressed = null;
|
package/src/web/debug.js
CHANGED
|
@@ -22,7 +22,7 @@ class MemoryView {
|
|
|
22
22
|
}
|
|
23
23
|
template.remove();
|
|
24
24
|
|
|
25
|
-
widget.
|
|
25
|
+
widget.on("wheel", (evt) => {
|
|
26
26
|
const deltaY = evt.originalEvent.deltaY;
|
|
27
27
|
if (deltaY === 0) return;
|
|
28
28
|
const steps = (deltaY / 20) | 0;
|
|
@@ -111,7 +111,7 @@ export class Debugger {
|
|
|
111
111
|
|
|
112
112
|
this.disass.find(".bp_gutter").click(this.bpClick.bind(this));
|
|
113
113
|
|
|
114
|
-
this.disass.
|
|
114
|
+
this.disass.on("wheel", (evt) => {
|
|
115
115
|
let deltaY = evt.originalEvent.deltaY;
|
|
116
116
|
if (deltaY === 0) return;
|
|
117
117
|
let addr = this.disassPc;
|
package/tests/test-machine.js
CHANGED
|
@@ -119,9 +119,12 @@ export class TestMachine {
|
|
|
119
119
|
|
|
120
120
|
const typeChar = (ch) => {
|
|
121
121
|
let shift = false;
|
|
122
|
+
// Map printable ASCII characters to BBC Micro key codes (UK layout).
|
|
123
|
+
// Characters not listed here fall through to the default case which
|
|
124
|
+
// uses toUpperCase().charCodeAt(0) — valid for A–Z and 0–9 only.
|
|
122
125
|
switch (ch) {
|
|
123
126
|
case '"':
|
|
124
|
-
ch =
|
|
127
|
+
ch = utils.keyCodes.K2;
|
|
125
128
|
shift = true;
|
|
126
129
|
break;
|
|
127
130
|
case "*":
|
|
@@ -148,7 +151,85 @@ export class TestMachine {
|
|
|
148
151
|
ch = utils.keyCodes.K6;
|
|
149
152
|
shift = true;
|
|
150
153
|
break;
|
|
154
|
+
case " ":
|
|
155
|
+
ch = utils.keyCodes.SPACE;
|
|
156
|
+
break;
|
|
157
|
+
case "-":
|
|
158
|
+
ch = utils.keyCodes.MINUS;
|
|
159
|
+
break;
|
|
160
|
+
case "=":
|
|
161
|
+
ch = utils.keyCodes.MINUS;
|
|
162
|
+
shift = true;
|
|
163
|
+
break;
|
|
164
|
+
case "+":
|
|
165
|
+
ch = utils.keyCodes.SEMICOLON;
|
|
166
|
+
shift = true;
|
|
167
|
+
break;
|
|
168
|
+
case "^":
|
|
169
|
+
ch = utils.keyCodes.EQUALS;
|
|
170
|
+
break;
|
|
171
|
+
case "~":
|
|
172
|
+
ch = utils.keyCodes.EQUALS;
|
|
173
|
+
shift = true;
|
|
174
|
+
break;
|
|
175
|
+
case "[":
|
|
176
|
+
ch = utils.keyCodes.LEFT_SQUARE_BRACKET;
|
|
177
|
+
break;
|
|
178
|
+
case "]":
|
|
179
|
+
ch = utils.keyCodes.RIGHT_SQUARE_BRACKET;
|
|
180
|
+
break;
|
|
181
|
+
case "{":
|
|
182
|
+
ch = utils.keyCodes.LEFT_SQUARE_BRACKET;
|
|
183
|
+
shift = true;
|
|
184
|
+
break;
|
|
185
|
+
case "}":
|
|
186
|
+
ch = utils.keyCodes.HASH;
|
|
187
|
+
shift = true;
|
|
188
|
+
break;
|
|
189
|
+
case "\\":
|
|
190
|
+
ch = utils.keyCodes.BACKSLASH;
|
|
191
|
+
break;
|
|
192
|
+
case "/":
|
|
193
|
+
ch = utils.keyCodes.SLASH;
|
|
194
|
+
break;
|
|
195
|
+
case "?":
|
|
196
|
+
ch = utils.keyCodes.SLASH;
|
|
197
|
+
shift = true;
|
|
198
|
+
break;
|
|
199
|
+
case "<":
|
|
200
|
+
ch = utils.keyCodes.COMMA;
|
|
201
|
+
shift = true;
|
|
202
|
+
break;
|
|
203
|
+
case ">":
|
|
204
|
+
ch = utils.keyCodes.PERIOD;
|
|
205
|
+
shift = true;
|
|
206
|
+
break;
|
|
207
|
+
case "(":
|
|
208
|
+
ch = utils.keyCodes.K8;
|
|
209
|
+
shift = true;
|
|
210
|
+
break;
|
|
211
|
+
case ")":
|
|
212
|
+
ch = utils.keyCodes.K9;
|
|
213
|
+
shift = true;
|
|
214
|
+
break;
|
|
215
|
+
case "@":
|
|
216
|
+
ch = utils.keyCodes.BACK_QUOTE;
|
|
217
|
+
break;
|
|
218
|
+
case "#":
|
|
219
|
+
ch = utils.keyCodes.K3;
|
|
220
|
+
shift = true;
|
|
221
|
+
break;
|
|
222
|
+
case "$":
|
|
223
|
+
ch = utils.keyCodes.K4;
|
|
224
|
+
shift = true;
|
|
225
|
+
break;
|
|
226
|
+
case "%":
|
|
227
|
+
ch = utils.keyCodes.K5;
|
|
228
|
+
shift = true;
|
|
229
|
+
break;
|
|
151
230
|
default:
|
|
231
|
+
// A-Z and 0-9: ASCII value == DOM keyCode — works as-is.
|
|
232
|
+
// Anything else (e.g. '_', '£', '`') is not yet mapped.
|
|
152
233
|
ch = ch.toUpperCase().charCodeAt(0);
|
|
153
234
|
break;
|
|
154
235
|
}
|
package/.editorconfig
DELETED
package/.git-blame-ignore-revs
DELETED