jsbeeb 1.1.1
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/.editorconfig +15 -0
- package/.git-blame-ignore-revs +3 -0
- package/.github/copilot-instructions.md +94 -0
- package/.github/workflows/claude-issue-triage.yml +105 -0
- package/.github/workflows/claude.yml +63 -0
- package/.github/workflows/release-please.yml +75 -0
- package/.github/workflows/test-and-deploy.yml +86 -0
- package/.gitmodules +6 -0
- package/.husky/pre-commit +1 -0
- package/.idea/codeStyleSettings.xml +9 -0
- package/.idea/codeStyles/Project.xml +62 -0
- package/.idea/codeStyles/codeStyleConfig.xml +5 -0
- package/.idea/compiler.xml +22 -0
- package/.idea/copyright/profiles_settings.xml +3 -0
- package/.idea/encodings.xml +6 -0
- package/.idea/inspectionProfiles/Project_Default.xml +7 -0
- package/.idea/jsLibraryMappings.xml +6 -0
- package/.idea/jsLinters/jshint.xml +85 -0
- package/.idea/jsLinters/jslint.xml +15 -0
- package/.idea/jsbeeb.iml +11 -0
- package/.idea/misc.xml +6 -0
- package/.idea/modules.xml +8 -0
- package/.idea/prettier.xml +7 -0
- package/.idea/runConfigurations/Debug.xml +5 -0
- package/.idea/scopes/scope_settings.xml +5 -0
- package/.idea/vcs.xml +8 -0
- package/.prettierignore +4 -0
- package/.prettierrc.json +1 -0
- package/.release-please-manifest.json +3 -0
- package/.vscode/launch.json +14 -0
- package/.vscode/settings.json +6 -0
- package/CHANGELOG.md +32 -0
- package/CLAUDE.md +136 -0
- package/COPYING +674 -0
- package/Dockerfile +22 -0
- package/Makefile +30 -0
- package/README.md +259 -0
- package/docker/nginx-default.conf +10 -0
- package/docs/pal-comb-filter-research.md +129 -0
- package/docs/pal-simulation-design.md +368 -0
- package/eslint.config.js +35 -0
- package/index.html +954 -0
- package/jsconfig.json +10 -0
- package/package.json +102 -0
- package/public/discs/README.Irq-Timing +3 -0
- package/public/discs/README.bcdtest +5 -0
- package/public/discs/README.elite +6 -0
- package/public/discs/README.eng_test +3 -0
- package/public/discs/README.protection +7 -0
- 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 +3 -0
- package/public/roms/ADFS1-53.rom +0 -0
- package/public/roms/BASIC.ROM +0 -0
- package/public/roms/README +4 -0
- 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 +68819 -0
- 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 +13 -0
- package/run-container.sh +92 -0
- package/src/6502.js +1347 -0
- package/src/6502.opcodes.js +1411 -0
- package/src/acia.js +261 -0
- package/src/adc.js +149 -0
- package/src/analogue-source.js +21 -0
- package/src/app/app.js +175 -0
- package/src/app/electron.js +20 -0
- package/src/app/preload.js +8 -0
- package/src/app-bench.js +33 -0
- package/src/basic/multiline-tetris +9 -0
- package/src/basic-tokenise.js +104 -0
- package/src/canvas.js +177 -0
- package/src/cmos.js +141 -0
- package/src/config.js +165 -0
- package/src/ddnoise.js +138 -0
- package/src/disc-drive.js +371 -0
- package/src/disc-hfe.js +396 -0
- package/src/disc.js +997 -0
- package/src/econet/L3FS.dat +0 -0
- package/src/econet/scsi.dat +0 -0
- package/src/econet.js +714 -0
- package/src/fake6502.js +32 -0
- package/src/fdc.js +248 -0
- package/src/filestore.js +666 -0
- package/src/gamepad-source.js +59 -0
- package/src/gamepads.js +268 -0
- package/src/google-drive.js +160 -0
- package/src/intel-fdc.js +1717 -0
- package/src/jsbeeb.css +363 -0
- package/src/keyboard.js +411 -0
- package/src/lib/README +1 -0
- package/src/lib/webgl-debug.js +911 -0
- package/src/main.js +1759 -0
- package/src/microphone-input.js +149 -0
- package/src/models.js +200 -0
- package/src/mouse-joystick-source.js +107 -0
- package/src/music5000-worklet.js +43 -0
- package/src/music5000.js +207 -0
- package/src/scheduler.js +148 -0
- package/src/serial.js +31 -0
- package/src/soundchip.js +314 -0
- package/src/sth.js +59 -0
- package/src/tapes.js +265 -0
- package/src/teletext.js +348 -0
- package/src/teletext_adaptor.js +172 -0
- package/src/teletext_data.js +1064 -0
- package/src/touchscreen.js +86 -0
- package/src/tube.js +349 -0
- package/src/url-params.js +256 -0
- package/src/utils.js +1090 -0
- package/src/via.js +702 -0
- package/src/video-filters/pal-composite.js +94 -0
- package/src/video-filters/passthrough-filter.js +70 -0
- package/src/video-filters/shaders/pal-composite.frag.glsl +147 -0
- package/src/video-filters/shaders/pal-composite.vert.glsl +8 -0
- package/src/video-filters/shaders/passthrough.frag.glsl +6 -0
- package/src/video-filters/shaders/passthrough.vert.glsl +7 -0
- package/src/video.js +794 -0
- package/src/wd-fdc.js +1344 -0
- package/src/web/audio-handler.js +146 -0
- package/src/web/audio-renderer.js +115 -0
- package/src/web/debug.js +529 -0
- package/tests/integration/RmwX.asm +47 -0
- package/tests/integration/TestInstructionsSource +27 -0
- package/tests/integration/TestTimingsResults +27 -0
- package/tests/integration/TestTimingsSource +61 -0
- package/tests/integration/bcd.js +23 -0
- package/tests/integration/dormann.js +101 -0
- package/tests/integration/dp111timing.js +42 -0
- package/tests/integration/ensure-submodules.js +25 -0
- package/tests/integration/nops.bas +119 -0
- package/tests/integration/nops.js +24 -0
- package/tests/integration/protection.js +26 -0
- package/tests/integration/rmw.js +69 -0
- 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 +126 -0
- package/tests/integration/timings.js +56 -0
- package/tests/integration/via.js +1125 -0
- package/tests/suite/README.md +7 -0
- package/tests/suite/Test Suite 2.15.txt +373 -0
- 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 +178 -0
- package/tests/suite/cbm-hackers-post.md +78 -0
- package/tests/test-machine.js +288 -0
- package/tests/test-suite.js +147 -0
- package/tests/test.css +7 -0
- 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 +307 -0
- package/tests/unit/test-bcd.js +30 -0
- package/tests/unit/test-cmos.js +266 -0
- package/tests/unit/test-disc-drive.js +85 -0
- package/tests/unit/test-disc-hfe.js +347 -0
- package/tests/unit/test-disc.js +232 -0
- package/tests/unit/test-fifo.js +35 -0
- package/tests/unit/test-gamepad-source.js +67 -0
- package/tests/unit/test-gzip.js +22 -0
- package/tests/unit/test-intel-fdc.js +93 -0
- package/tests/unit/test-keyboard.js +410 -0
- package/tests/unit/test-mouse-joystick-source.js +128 -0
- package/tests/unit/test-scheduler.js +190 -0
- package/tests/unit/test-serial.js +154 -0
- package/tests/unit/test-teletext-adaptor.js +359 -0
- package/tests/unit/test-tokenise.js +65 -0
- package/tests/unit/test-url-params.js +398 -0
- package/tests/unit/test-utils.js +276 -0
- package/tests/unit/test-video.js +498 -0
- package/tests/unit/test-zip.js +56 -0
- 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 +80 -0
- package/tools/vite-plugin-fir-shader.js +131 -0
- package/vite.config.js +34 -0
|
@@ -0,0 +1,347 @@
|
|
|
1
|
+
import { describe, it, expect } from "vitest";
|
|
2
|
+
|
|
3
|
+
import { Disc, DiscConfig, IbmDiscFormat, loadSsd } from "../../src/disc.js";
|
|
4
|
+
import { loadHfe, toHfe, convertTrackToHfeV3 } from "../../src/disc-hfe.js";
|
|
5
|
+
import * as fs from "node:fs";
|
|
6
|
+
|
|
7
|
+
describe("HFE loader tests", function () {
|
|
8
|
+
const data = fs.readFileSync("public/discs/elite.hfe");
|
|
9
|
+
it("should load Elite", () => {
|
|
10
|
+
const disc = new Disc(true, new DiscConfig(), "test.hfe");
|
|
11
|
+
loadHfe(disc, data);
|
|
12
|
+
expect(disc.tracksUsed).toBe(81);
|
|
13
|
+
const sectors = disc.getTrack(false, 0).findSectors();
|
|
14
|
+
expect(sectors.length).toBe(10);
|
|
15
|
+
for (const sector of sectors) {
|
|
16
|
+
expect(sector.hasHeaderCrcError).toBe(false);
|
|
17
|
+
expect(sector.hasDataCrcError).toBe(false);
|
|
18
|
+
}
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
it("should reject invalid HFE files", () => {
|
|
22
|
+
const disc = new Disc(true, new DiscConfig(), "test.hfe");
|
|
23
|
+
|
|
24
|
+
// Test missing header
|
|
25
|
+
expect(() => {
|
|
26
|
+
loadHfe(disc, new Uint8Array(10));
|
|
27
|
+
}).toThrow(/HFE file missing header/);
|
|
28
|
+
|
|
29
|
+
// Test invalid header
|
|
30
|
+
const invalidHeader = new Uint8Array(512);
|
|
31
|
+
invalidHeader.set(new TextEncoder().encode("INVALID!"), 0);
|
|
32
|
+
expect(() => {
|
|
33
|
+
loadHfe(disc, invalidHeader);
|
|
34
|
+
}).toThrow(/HFE file bad header/);
|
|
35
|
+
|
|
36
|
+
// Test non-zero revision
|
|
37
|
+
const nonZeroRevision = new Uint8Array(512);
|
|
38
|
+
nonZeroRevision.set(new TextEncoder().encode("HXCHFEV3"), 0);
|
|
39
|
+
nonZeroRevision[8] = 1; // Set revision to 1 (should be 0)
|
|
40
|
+
expect(() => {
|
|
41
|
+
loadHfe(disc, nonZeroRevision);
|
|
42
|
+
}).toThrow(/HFE file revision not 0/);
|
|
43
|
+
|
|
44
|
+
// Test unsupported encoding
|
|
45
|
+
const unsupportedEncoding = new Uint8Array(512);
|
|
46
|
+
unsupportedEncoding.set(new TextEncoder().encode("HXCHFEV3"), 0);
|
|
47
|
+
unsupportedEncoding[8] = 0; // Revision 0
|
|
48
|
+
unsupportedEncoding[11] = 1; // Encoding 1 (not 0 or 2)
|
|
49
|
+
expect(() => {
|
|
50
|
+
loadHfe(disc, unsupportedEncoding);
|
|
51
|
+
}).toThrow(/HFE encoding not ISOIBM/);
|
|
52
|
+
});
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
describe(
|
|
56
|
+
"HFE round-trip tests",
|
|
57
|
+
{
|
|
58
|
+
timeout: 120000, // HFE processing can be slow
|
|
59
|
+
},
|
|
60
|
+
function () {
|
|
61
|
+
const data = fs.readFileSync("public/discs/elite.hfe");
|
|
62
|
+
it("should round-trip elite.hfe", () => {
|
|
63
|
+
// Load the original HFE file
|
|
64
|
+
const disc = new Disc(true, new DiscConfig(), "test.hfe");
|
|
65
|
+
loadHfe(disc, data);
|
|
66
|
+
|
|
67
|
+
// Export it back to HFE
|
|
68
|
+
const hfeSaved = toHfe(disc);
|
|
69
|
+
|
|
70
|
+
// Load the saved HFE into a new disc
|
|
71
|
+
const disc2 = new Disc(true, new DiscConfig(), "test2.hfe");
|
|
72
|
+
loadHfe(disc2, hfeSaved);
|
|
73
|
+
|
|
74
|
+
// Verify that both discs have the same properties
|
|
75
|
+
expect(disc.tracksUsed).toBe(disc2.tracksUsed);
|
|
76
|
+
expect(disc.isDoubleSided).toBe(disc2.isDoubleSided);
|
|
77
|
+
|
|
78
|
+
// Compare sectors in a few sample tracks
|
|
79
|
+
const trackSamples = [0, 10, 20, 40]; // Sample a few tracks
|
|
80
|
+
for (const trackNum of trackSamples) {
|
|
81
|
+
if (trackNum >= disc.tracksUsed) continue;
|
|
82
|
+
|
|
83
|
+
const track1 = disc.getTrack(false, trackNum);
|
|
84
|
+
const track2 = disc2.getTrack(false, trackNum);
|
|
85
|
+
|
|
86
|
+
// With our variable track length HFE implementation,
|
|
87
|
+
// track lengths should be identical after roundtripping
|
|
88
|
+
|
|
89
|
+
// Track lengths should be identical when roundtripping with the variable track length HFE implementation
|
|
90
|
+
expect(track1.length).toBe(track2.length);
|
|
91
|
+
|
|
92
|
+
// Compare sectors - this is the most important test
|
|
93
|
+
// All sectors must be readable and contain the correct data
|
|
94
|
+
const sectors1 = track1.findSectors();
|
|
95
|
+
const sectors2 = track2.findSectors();
|
|
96
|
+
|
|
97
|
+
// All sectors must be found
|
|
98
|
+
expect(sectors1.length).toBe(sectors2.length);
|
|
99
|
+
|
|
100
|
+
// Compare sector data for first sector as a sample
|
|
101
|
+
if (sectors1.length > 0 && sectors2.length > 0) {
|
|
102
|
+
expect(sectors1[0].sectorNumber).toBe(sectors2[0].sectorNumber);
|
|
103
|
+
expect(sectors1[0].trackNumber).toBe(sectors2[0].trackNumber);
|
|
104
|
+
|
|
105
|
+
// Compare actual sector data if available
|
|
106
|
+
if (sectors1[0].sectorData && sectors2[0].sectorData) {
|
|
107
|
+
expect(sectors1[0].sectorData.length).toBe(sectors2[0].sectorData.length);
|
|
108
|
+
|
|
109
|
+
// Sample a few bytes from the sector
|
|
110
|
+
if (sectors1[0].sectorData.length > 0) {
|
|
111
|
+
expect(sectors1[0].sectorData[0]).toBe(sectors2[0].sectorData[0]);
|
|
112
|
+
|
|
113
|
+
const midPoint = Math.floor(sectors1[0].sectorData.length / 2);
|
|
114
|
+
expect(sectors1[0].sectorData[midPoint]).toBe(sectors2[0].sectorData[midPoint]);
|
|
115
|
+
|
|
116
|
+
expect(sectors1[0].sectorData[sectors1[0].sectorData.length - 1]).toBe(
|
|
117
|
+
sectors2[0].sectorData[sectors2[0].sectorData.length - 1],
|
|
118
|
+
);
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
});
|
|
124
|
+
},
|
|
125
|
+
);
|
|
126
|
+
|
|
127
|
+
describe("HFE export tests", function () {
|
|
128
|
+
it("should export a simple single-sided disc", { timeout: 10000 }, () => {
|
|
129
|
+
const disc = new Disc(true, new DiscConfig(), "test.hfe");
|
|
130
|
+
const sectorData = new Uint8Array(256);
|
|
131
|
+
sectorData.fill(0xa5);
|
|
132
|
+
|
|
133
|
+
// Create a simple FM track
|
|
134
|
+
const builder = disc.buildTrack(false, 0);
|
|
135
|
+
builder
|
|
136
|
+
.appendRepeatFmByte(0xff, IbmDiscFormat.stdGap1FFs)
|
|
137
|
+
.appendRepeatFmByte(0x00, IbmDiscFormat.stdSync00s)
|
|
138
|
+
.resetCrc()
|
|
139
|
+
.appendFmDataAndClocks(IbmDiscFormat.idMarkDataPattern, IbmDiscFormat.markClockPattern)
|
|
140
|
+
.appendFmByte(0) // track
|
|
141
|
+
.appendFmByte(0)
|
|
142
|
+
.appendFmByte(0) // sector
|
|
143
|
+
.appendFmByte(1)
|
|
144
|
+
.appendCrc(false)
|
|
145
|
+
.appendRepeatFmByte(0xff, IbmDiscFormat.stdGap2FFs)
|
|
146
|
+
.appendRepeatFmByte(0x00, IbmDiscFormat.stdSync00s)
|
|
147
|
+
.resetCrc()
|
|
148
|
+
.appendFmDataAndClocks(IbmDiscFormat.dataMarkDataPattern, IbmDiscFormat.markClockPattern)
|
|
149
|
+
.appendFmChunk(sectorData)
|
|
150
|
+
.appendCrc(false)
|
|
151
|
+
.fillFmByte(0xff);
|
|
152
|
+
|
|
153
|
+
const hfeData = toHfe(disc);
|
|
154
|
+
|
|
155
|
+
// Verify HFE header
|
|
156
|
+
const header = new TextDecoder("ascii").decode(hfeData.slice(0, 8));
|
|
157
|
+
expect(header).toBe("HXCHFEV3");
|
|
158
|
+
expect(hfeData[8]).toBe(0); // Revision
|
|
159
|
+
expect(hfeData[9]).toBe(1); // Number of tracks
|
|
160
|
+
expect(hfeData[10]).toBe(1); // Number of sides
|
|
161
|
+
expect(hfeData[11]).toBe(2); // ISOIBM_FM_MFM_ENCODING
|
|
162
|
+
});
|
|
163
|
+
|
|
164
|
+
it("should export and reload the same disc", { timeout: 30000 }, () => {
|
|
165
|
+
// Create a disc with FM data
|
|
166
|
+
const disc = new Disc(true, new DiscConfig(), "test.hfe");
|
|
167
|
+
const data = new Uint8Array(10240); // 10 sectors worth
|
|
168
|
+
for (let i = 0; i < data.length; i++) {
|
|
169
|
+
data[i] = i & 0xff;
|
|
170
|
+
}
|
|
171
|
+
loadSsd(disc, data, false);
|
|
172
|
+
|
|
173
|
+
// Export to HFE
|
|
174
|
+
const hfeData = toHfe(disc);
|
|
175
|
+
|
|
176
|
+
// Reload from HFE
|
|
177
|
+
const disc2 = new Disc(true, new DiscConfig(), "test2.hfe");
|
|
178
|
+
loadHfe(disc2, hfeData);
|
|
179
|
+
|
|
180
|
+
// Compare track data
|
|
181
|
+
expect(disc.tracksUsed).toBe(disc2.tracksUsed);
|
|
182
|
+
for (let trackNum = 0; trackNum < disc.tracksUsed; trackNum++) {
|
|
183
|
+
const track1 = disc.getTrack(false, trackNum);
|
|
184
|
+
const track2 = disc2.getTrack(false, trackNum);
|
|
185
|
+
|
|
186
|
+
// With our improved implementation, track lengths should be exactly the same
|
|
187
|
+
// when round-tripping through the HFE format
|
|
188
|
+
expect(track1.length).toBe(track2.length);
|
|
189
|
+
|
|
190
|
+
// Find sectors and compare
|
|
191
|
+
const sectors1 = track1.findSectors();
|
|
192
|
+
const sectors2 = track2.findSectors();
|
|
193
|
+
|
|
194
|
+
// All sectors must be found - this is the critical test
|
|
195
|
+
expect(sectors1.length).toBe(sectors2.length);
|
|
196
|
+
|
|
197
|
+
expect(sectors1.length).toBe(sectors2.length);
|
|
198
|
+
|
|
199
|
+
for (let i = 0; i < sectors1.length; i++) {
|
|
200
|
+
expect(sectors1[i].sectorNumber).toBe(sectors2[i].sectorNumber);
|
|
201
|
+
expect(sectors1[i].trackNumber).toBe(sectors2[i].trackNumber);
|
|
202
|
+
// Compare sector data if available
|
|
203
|
+
if (sectors1[i].sectorData && sectors2[i].sectorData) {
|
|
204
|
+
expect(sectors1[i].sectorData).toEqual(sectors2[i].sectorData);
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
});
|
|
209
|
+
|
|
210
|
+
it("should export a double-sided disc", { timeout: 10000 }, () => {
|
|
211
|
+
const disc = new Disc(true, new DiscConfig(), "test.hfe");
|
|
212
|
+
const data = new Uint8Array(10240); // 10 sectors worth
|
|
213
|
+
data.fill(0xbb);
|
|
214
|
+
loadSsd(disc, data, true); // Load as DSD (double-sided)
|
|
215
|
+
|
|
216
|
+
const hfeData = toHfe(disc);
|
|
217
|
+
|
|
218
|
+
// Verify HFE header
|
|
219
|
+
const header = new TextDecoder("ascii").decode(hfeData.slice(0, 8));
|
|
220
|
+
expect(header).toBe("HXCHFEV3");
|
|
221
|
+
expect(hfeData[10]).toBe(2); // Number of sides
|
|
222
|
+
|
|
223
|
+
// Reload and verify
|
|
224
|
+
const disc2 = new Disc(true, new DiscConfig(), "test2.hfe");
|
|
225
|
+
loadHfe(disc2, hfeData);
|
|
226
|
+
|
|
227
|
+
expect(disc2.isDoubleSided).toBe(true);
|
|
228
|
+
expect(disc.tracksUsed).toBe(disc2.tracksUsed);
|
|
229
|
+
});
|
|
230
|
+
|
|
231
|
+
it("should properly handle MFM data", { timeout: 10000 }, () => {
|
|
232
|
+
const disc = new Disc(true, new DiscConfig(), "test.hfe");
|
|
233
|
+
const sectorData = new Uint8Array(256);
|
|
234
|
+
for (let i = 0; i < sectorData.length; i++) {
|
|
235
|
+
sectorData[i] = (i * 0x11) & 0xff;
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
// Create an MFM track
|
|
239
|
+
const builder = disc.buildTrack(false, 0);
|
|
240
|
+
builder
|
|
241
|
+
.appendRepeatMfmByte(0x4e, 60)
|
|
242
|
+
.appendRepeatMfmByte(0x00, 12)
|
|
243
|
+
.resetCrc()
|
|
244
|
+
.appendMfm3xA1Sync()
|
|
245
|
+
.appendMfmByte(IbmDiscFormat.idMarkDataPattern)
|
|
246
|
+
.appendMfmByte(0) // track
|
|
247
|
+
.appendMfmByte(0)
|
|
248
|
+
.appendMfmByte(0) // sector
|
|
249
|
+
.appendMfmByte(1)
|
|
250
|
+
.appendCrc(true)
|
|
251
|
+
.appendRepeatMfmByte(0x4e, 22)
|
|
252
|
+
.appendRepeatMfmByte(0x00, 12)
|
|
253
|
+
.resetCrc()
|
|
254
|
+
.appendMfm3xA1Sync()
|
|
255
|
+
.appendMfmByte(IbmDiscFormat.dataMarkDataPattern)
|
|
256
|
+
.appendMfmChunk(sectorData)
|
|
257
|
+
.appendCrc(true)
|
|
258
|
+
.appendRepeatMfmByte(0x4e, 24)
|
|
259
|
+
.fillMfmByte(0x4e);
|
|
260
|
+
|
|
261
|
+
const hfeData = toHfe(disc);
|
|
262
|
+
|
|
263
|
+
// Reload and verify
|
|
264
|
+
const disc2 = new Disc(true, new DiscConfig(), "test2.hfe");
|
|
265
|
+
loadHfe(disc2, hfeData);
|
|
266
|
+
|
|
267
|
+
const sectors = disc2.getTrack(false, 0).findSectors();
|
|
268
|
+
expect(sectors.length).toBe(1);
|
|
269
|
+
expect(sectors[0].isMfm).toBe(true);
|
|
270
|
+
expect(sectors[0].sectorData).toEqual(sectorData);
|
|
271
|
+
});
|
|
272
|
+
});
|
|
273
|
+
|
|
274
|
+
describe("HFE track conversion tests", function () {
|
|
275
|
+
it("should handle weak pulses correctly", () => {
|
|
276
|
+
// Create an array with some normal pulses and some weak pulses (0)
|
|
277
|
+
const pulses = [0xaabbccdd, 0, 0x11223344, 0x55667788, 0];
|
|
278
|
+
|
|
279
|
+
// Convert to HFE v3 format
|
|
280
|
+
const hfeData = convertTrackToHfeV3(pulses);
|
|
281
|
+
|
|
282
|
+
// Check for the track header (SETINDEX, SETBITRATE, Bitrate250k)
|
|
283
|
+
expect(hfeData.length).toBe(3 + pulses.length * 4);
|
|
284
|
+
|
|
285
|
+
// Check that weak pulses (value 0) are handled specially
|
|
286
|
+
// They should be encoded as the RAND opcode (0xF4) with bit flipping
|
|
287
|
+
// The RAND opcode after bit flipping is 0x2F
|
|
288
|
+
const randOpcodeFlipped = 0x2f;
|
|
289
|
+
|
|
290
|
+
// Check the second pulse (index 1) which is a weak pulse
|
|
291
|
+
expect(hfeData[3 + 4]).toBe(randOpcodeFlipped);
|
|
292
|
+
expect(hfeData[3 + 5]).toBe(randOpcodeFlipped);
|
|
293
|
+
expect(hfeData[3 + 6]).toBe(randOpcodeFlipped);
|
|
294
|
+
expect(hfeData[3 + 7]).toBe(randOpcodeFlipped);
|
|
295
|
+
|
|
296
|
+
// Also check the fifth pulse (index 4) which is also a weak pulse
|
|
297
|
+
expect(hfeData[3 + 16]).toBe(randOpcodeFlipped);
|
|
298
|
+
expect(hfeData[3 + 17]).toBe(randOpcodeFlipped);
|
|
299
|
+
expect(hfeData[3 + 18]).toBe(randOpcodeFlipped);
|
|
300
|
+
expect(hfeData[3 + 19]).toBe(randOpcodeFlipped);
|
|
301
|
+
});
|
|
302
|
+
|
|
303
|
+
it("should replace pulses with first byte 0xf0 to avoid collision", () => {
|
|
304
|
+
// When bit-flipped, 0xf0 becomes 0x0f, which triggers opcode collision
|
|
305
|
+
// The RAND opcode (0xf4) bit-flipped is 0x2f (47 decimal)
|
|
306
|
+
const randOpcodeFlipped = 0x2f;
|
|
307
|
+
|
|
308
|
+
// Create a pulse with 0xf0 as the first byte
|
|
309
|
+
const pulseWithF0 = 0xf0aabbcc;
|
|
310
|
+
|
|
311
|
+
// Convert to HFE v3 format
|
|
312
|
+
const hfeData = convertTrackToHfeV3([pulseWithF0]);
|
|
313
|
+
|
|
314
|
+
// When we detect this kind of collision, we replace with the bit-flipped RAND opcode
|
|
315
|
+
expect(hfeData[3]).toBe(randOpcodeFlipped);
|
|
316
|
+
});
|
|
317
|
+
|
|
318
|
+
it("should handle bit flipping for pulses starting with 0x0f", () => {
|
|
319
|
+
// Create a pulse with 0x0f as most significant byte
|
|
320
|
+
const pulseWithF = 0x0f000000;
|
|
321
|
+
|
|
322
|
+
// Convert to HFE v3 format
|
|
323
|
+
const hfeData = convertTrackToHfeV3([pulseWithF]);
|
|
324
|
+
|
|
325
|
+
// The flipped value (0xf0) is returned directly (no opcode collision detected)
|
|
326
|
+
// because 0xf0 doesn't match the collision detection pattern
|
|
327
|
+
expect(hfeData[3]).toBe(0xf0);
|
|
328
|
+
});
|
|
329
|
+
|
|
330
|
+
it("should preserve normal pulses correctly", () => {
|
|
331
|
+
// Create a normal pulse that doesn't have opcode collisions
|
|
332
|
+
const normalPulse = 0x12345678;
|
|
333
|
+
|
|
334
|
+
// Convert to HFE v3 format
|
|
335
|
+
const hfeData = convertTrackToHfeV3([normalPulse]);
|
|
336
|
+
|
|
337
|
+
// Check the bytes match what we expect after bit flipping
|
|
338
|
+
// 0x12 after bit flipping becomes 0x48
|
|
339
|
+
// 0x34 after bit flipping becomes 0x2C
|
|
340
|
+
// 0x56 after bit flipping becomes 0x6A
|
|
341
|
+
// 0x78 after bit flipping becomes 0x1E
|
|
342
|
+
expect(hfeData[3]).toBe(0x48);
|
|
343
|
+
expect(hfeData[4]).toBe(0x2c);
|
|
344
|
+
expect(hfeData[5]).toBe(0x6a);
|
|
345
|
+
expect(hfeData[6]).toBe(0x1e);
|
|
346
|
+
});
|
|
347
|
+
});
|
|
@@ -0,0 +1,232 @@
|
|
|
1
|
+
import { describe, it, expect } from "vitest";
|
|
2
|
+
|
|
3
|
+
import { Disc, DiscConfig, IbmDiscFormat, loadSsd, loadAdf, toSsdOrDsd } from "../../src/disc.js";
|
|
4
|
+
import * as fs from "node:fs";
|
|
5
|
+
|
|
6
|
+
describe("IBM disc format tests", function () {
|
|
7
|
+
it("calculates FM crcs", () => {
|
|
8
|
+
let crc = IbmDiscFormat.crcInit(false);
|
|
9
|
+
crc = IbmDiscFormat.crcAddByte(crc, 0x12);
|
|
10
|
+
crc = IbmDiscFormat.crcAddByte(crc, 0x34);
|
|
11
|
+
crc = IbmDiscFormat.crcAddByte(crc, 0x56);
|
|
12
|
+
crc = IbmDiscFormat.crcAddByte(crc, 0x70);
|
|
13
|
+
expect(crc).toBe(0xb1e4);
|
|
14
|
+
});
|
|
15
|
+
it("calculates MFM crcs", () => {
|
|
16
|
+
let crc = IbmDiscFormat.crcInit(true);
|
|
17
|
+
crc = IbmDiscFormat.crcAddByte(crc, 0x12);
|
|
18
|
+
crc = IbmDiscFormat.crcAddByte(crc, 0x34);
|
|
19
|
+
crc = IbmDiscFormat.crcAddByte(crc, 0x56);
|
|
20
|
+
crc = IbmDiscFormat.crcAddByte(crc, 0x70);
|
|
21
|
+
expect(crc).toBe(0x9d39);
|
|
22
|
+
});
|
|
23
|
+
it("converts to FM pulses", () => {
|
|
24
|
+
expect(IbmDiscFormat.fmTo2usPulses(0xff, 0x00)).toBe(0x44444444);
|
|
25
|
+
expect(IbmDiscFormat.fmTo2usPulses(0xff, 0xff)).toBe(0x55555555);
|
|
26
|
+
expect(IbmDiscFormat.fmTo2usPulses(0xc7, 0xfe)).toBe(0x55111554);
|
|
27
|
+
});
|
|
28
|
+
it("converts from FM pulses", () => {
|
|
29
|
+
// TODO either fix these or understand why beebjit doesn't use same bit posn for bits.
|
|
30
|
+
const deliberateFudge = 1;
|
|
31
|
+
expect(IbmDiscFormat._2usPulsesToFm(0x44444444 << deliberateFudge)).toEqual({
|
|
32
|
+
clocks: 0xff,
|
|
33
|
+
data: 0x00,
|
|
34
|
+
iffyPulses: false,
|
|
35
|
+
});
|
|
36
|
+
expect(IbmDiscFormat._2usPulsesToFm(0x55555555 << deliberateFudge)).toEqual({
|
|
37
|
+
clocks: 0xff,
|
|
38
|
+
data: 0xff,
|
|
39
|
+
iffyPulses: false,
|
|
40
|
+
});
|
|
41
|
+
expect(IbmDiscFormat._2usPulsesToFm(0x55111554 << deliberateFudge)).toEqual({
|
|
42
|
+
clocks: 0xc7,
|
|
43
|
+
data: 0xfe,
|
|
44
|
+
iffyPulses: false,
|
|
45
|
+
});
|
|
46
|
+
expect(IbmDiscFormat._2usPulsesToFm((0x55111554 << deliberateFudge) | 0x05)).toEqual({
|
|
47
|
+
clocks: 0xc7,
|
|
48
|
+
data: 0xfe,
|
|
49
|
+
iffyPulses: true,
|
|
50
|
+
});
|
|
51
|
+
});
|
|
52
|
+
it("converts to MFM pulses", () => {
|
|
53
|
+
expect(IbmDiscFormat.mfmTo2usPulses(false, 0x00)).toEqual({ lastBit: false, pulses: 0xaaaa });
|
|
54
|
+
expect(IbmDiscFormat.mfmTo2usPulses(true, 0x00)).toEqual({ lastBit: false, pulses: 0x2aaa });
|
|
55
|
+
expect(IbmDiscFormat.mfmTo2usPulses(false, 0xff)).toEqual({ lastBit: true, pulses: 0x5555 });
|
|
56
|
+
expect(IbmDiscFormat.mfmTo2usPulses(true, 0xff)).toEqual({ lastBit: true, pulses: 0x5555 });
|
|
57
|
+
expect(IbmDiscFormat.mfmTo2usPulses(false, 0x37)).toEqual({ lastBit: true, pulses: 0xa515 });
|
|
58
|
+
expect(IbmDiscFormat.mfmTo2usPulses(true, 0x37)).toEqual({ lastBit: true, pulses: 0x2515 });
|
|
59
|
+
});
|
|
60
|
+
it("Converts from MFM pulses", () => {
|
|
61
|
+
expect(IbmDiscFormat._2usPulsesToMfm(0xaaaa)).toBe(0);
|
|
62
|
+
expect(IbmDiscFormat._2usPulsesToMfm(0x2aaa)).toBe(0);
|
|
63
|
+
expect(IbmDiscFormat._2usPulsesToMfm(0x5555)).toBe(0xff);
|
|
64
|
+
expect(IbmDiscFormat._2usPulsesToMfm(0xa515)).toBe(0x37);
|
|
65
|
+
expect(IbmDiscFormat._2usPulsesToMfm(0x2515)).toBe(0x37);
|
|
66
|
+
});
|
|
67
|
+
it("checks gaps between MFM pulses", () => {
|
|
68
|
+
expect(IbmDiscFormat.checkPulse(0.0, true)).toBe(false);
|
|
69
|
+
expect(IbmDiscFormat.checkPulse(3.49, true)).toBe(false);
|
|
70
|
+
expect(IbmDiscFormat.checkPulse(4.51, true)).toBe(false);
|
|
71
|
+
expect(IbmDiscFormat.checkPulse(7.49, true)).toBe(false);
|
|
72
|
+
expect(IbmDiscFormat.checkPulse(8.51, true)).toBe(false);
|
|
73
|
+
|
|
74
|
+
expect(IbmDiscFormat.checkPulse(4.0, true)).toBe(true);
|
|
75
|
+
expect(IbmDiscFormat.checkPulse(5.51, true)).toBe(true);
|
|
76
|
+
expect(IbmDiscFormat.checkPulse(6.0, true)).toBe(true);
|
|
77
|
+
expect(IbmDiscFormat.checkPulse(6.49, true)).toBe(true);
|
|
78
|
+
expect(IbmDiscFormat.checkPulse(8.0, true)).toBe(true);
|
|
79
|
+
});
|
|
80
|
+
it("checks gaps between FM pulses", () => {
|
|
81
|
+
expect(IbmDiscFormat.checkPulse(0.0, false)).toBe(false);
|
|
82
|
+
expect(IbmDiscFormat.checkPulse(3.49, false)).toBe(false);
|
|
83
|
+
expect(IbmDiscFormat.checkPulse(4.51, false)).toBe(false);
|
|
84
|
+
expect(IbmDiscFormat.checkPulse(5.51, false)).toBe(false);
|
|
85
|
+
expect(IbmDiscFormat.checkPulse(6.0, false)).toBe(false);
|
|
86
|
+
expect(IbmDiscFormat.checkPulse(6.49, false)).toBe(false);
|
|
87
|
+
expect(IbmDiscFormat.checkPulse(7.49, false)).toBe(false);
|
|
88
|
+
expect(IbmDiscFormat.checkPulse(8.51, false)).toBe(false);
|
|
89
|
+
|
|
90
|
+
expect(IbmDiscFormat.checkPulse(4.0, false)).toBe(true);
|
|
91
|
+
expect(IbmDiscFormat.checkPulse(8.0, false)).toBe(true);
|
|
92
|
+
});
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
describe("Disc builder tests", () => {
|
|
96
|
+
const someData = new Uint8Array(256);
|
|
97
|
+
someData.fill(0x33);
|
|
98
|
+
it("should write a simple FM track without blowing up", () => {
|
|
99
|
+
const disc = new Disc(true, new DiscConfig(), "test.ssd");
|
|
100
|
+
const builder = disc.buildTrack(false, 0);
|
|
101
|
+
builder
|
|
102
|
+
.appendRepeatFmByte(0xff, IbmDiscFormat.stdGap1FFs)
|
|
103
|
+
.appendRepeatFmByte(0x00, IbmDiscFormat.stdSync00s)
|
|
104
|
+
.resetCrc()
|
|
105
|
+
.appendFmDataAndClocks(IbmDiscFormat.idMarkDataPattern, IbmDiscFormat.markClockPattern)
|
|
106
|
+
.appendFmByte(0) // track
|
|
107
|
+
.appendFmByte(0)
|
|
108
|
+
.appendFmByte(0) // sector
|
|
109
|
+
.appendFmByte(1)
|
|
110
|
+
.appendCrc(false)
|
|
111
|
+
.appendRepeatFmByte(0xff, IbmDiscFormat.stdGap2FFs)
|
|
112
|
+
.appendRepeatFmByte(0x00, IbmDiscFormat.stdSync00s)
|
|
113
|
+
.resetCrc()
|
|
114
|
+
.appendFmDataAndClocks(IbmDiscFormat.dataMarkDataPattern, IbmDiscFormat.markClockPattern)
|
|
115
|
+
.appendFmChunk(someData)
|
|
116
|
+
.appendCrc(false)
|
|
117
|
+
.fillFmByte(0xff);
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
it("should write a simple MFM track without blowing up", () => {
|
|
121
|
+
const disc = new Disc(true, new DiscConfig());
|
|
122
|
+
const builder = disc.buildTrack(false, 0);
|
|
123
|
+
builder
|
|
124
|
+
.appendRepeatMfmByte(0x4e, 60)
|
|
125
|
+
.appendRepeatMfmByte(0x00, 12)
|
|
126
|
+
.resetCrc()
|
|
127
|
+
.appendMfm3xA1Sync()
|
|
128
|
+
.appendMfmByte(IbmDiscFormat.idMarkDataPattern)
|
|
129
|
+
.appendMfmByte(0) // track
|
|
130
|
+
.appendMfmByte(0)
|
|
131
|
+
.appendMfmByte(0) // sector
|
|
132
|
+
.appendMfmByte(1)
|
|
133
|
+
.appendCrc(true)
|
|
134
|
+
.appendRepeatMfmByte(0x4e, 22)
|
|
135
|
+
.appendRepeatMfmByte(0x00, 12)
|
|
136
|
+
.resetCrc()
|
|
137
|
+
.appendMfm3xA1Sync()
|
|
138
|
+
.appendMfmByte(IbmDiscFormat.dataMarkDataPattern)
|
|
139
|
+
.appendMfmChunk(someData)
|
|
140
|
+
.appendCrc(true)
|
|
141
|
+
.appendRepeatMfmByte(0x4e, 24)
|
|
142
|
+
.fillMfmByte(0x4e);
|
|
143
|
+
});
|
|
144
|
+
|
|
145
|
+
it("should note how much disc is being used", () => {
|
|
146
|
+
const disc = new Disc(true, new DiscConfig(), "test.ssd");
|
|
147
|
+
expect(disc.tracksUsed).toBe(0);
|
|
148
|
+
expect(disc.isDoubleSided).toBe(false);
|
|
149
|
+
disc.buildTrack(false, 0);
|
|
150
|
+
expect(disc.tracksUsed).toBe(1);
|
|
151
|
+
expect(disc.isDoubleSided).toBe(false);
|
|
152
|
+
disc.buildTrack(false, 3);
|
|
153
|
+
expect(disc.tracksUsed).toBe(4);
|
|
154
|
+
expect(disc.isDoubleSided).toBe(false);
|
|
155
|
+
disc.buildTrack(true, 1);
|
|
156
|
+
expect(disc.tracksUsed).toBe(4);
|
|
157
|
+
expect(disc.isDoubleSided).toBe(true);
|
|
158
|
+
});
|
|
159
|
+
|
|
160
|
+
it("should build from FM pulses", () => {
|
|
161
|
+
const disc = new Disc(true, new DiscConfig(), "test.ssd");
|
|
162
|
+
const builder = disc.buildTrack(false, 0);
|
|
163
|
+
const pulses = [4, 4, 8, 8, 4, 8, 8, 8, 8, 8, 8, 8, 8];
|
|
164
|
+
builder.buildFromPulses(pulses, false);
|
|
165
|
+
expect(builder.track.length).toBe(1);
|
|
166
|
+
});
|
|
167
|
+
|
|
168
|
+
it("should build from MFM pulses", () => {
|
|
169
|
+
const disc = new Disc(true, new DiscConfig(), "test.ssd");
|
|
170
|
+
const builder = disc.buildTrack(false, 0);
|
|
171
|
+
const pulses = [4, 4, 6, 6, 4, 6, 6, 6, 6, 6, 6, 6, 6, 6];
|
|
172
|
+
builder.buildFromPulses(pulses, true);
|
|
173
|
+
expect(builder.track.length).toBe(1);
|
|
174
|
+
});
|
|
175
|
+
});
|
|
176
|
+
|
|
177
|
+
describe(
|
|
178
|
+
"SSD loader tests",
|
|
179
|
+
{
|
|
180
|
+
timeout: 60000, // roundtripping elite can be slow
|
|
181
|
+
},
|
|
182
|
+
function () {
|
|
183
|
+
const data = fs.readFileSync("public/discs/elite.ssd");
|
|
184
|
+
it("should load Elite", () => {
|
|
185
|
+
const disc = new Disc(true, new DiscConfig(), "test.ssd");
|
|
186
|
+
loadSsd(disc, data, false);
|
|
187
|
+
expect(disc.tracksUsed).toBe(80);
|
|
188
|
+
});
|
|
189
|
+
it("should roundtrip Elite", () => {
|
|
190
|
+
const disc = new Disc(true, new DiscConfig(), "test.ssd");
|
|
191
|
+
loadSsd(disc, data, false);
|
|
192
|
+
const ssdSaved = toSsdOrDsd(disc);
|
|
193
|
+
// // Check the first few bytes, else a diff blows things up
|
|
194
|
+
const maxDiff = 50;
|
|
195
|
+
expect(ssdSaved.slice(0, maxDiff)).toEqual(new Uint8Array(data.slice(0, maxDiff)));
|
|
196
|
+
|
|
197
|
+
// But also check everything else; and the padding should be all zeros.
|
|
198
|
+
expect(ssdSaved.length >= data.length).toBe(true);
|
|
199
|
+
for (let i = 0; i < data.length; ++i) {
|
|
200
|
+
expect(ssdSaved[i]).toBe(data[i]);
|
|
201
|
+
}
|
|
202
|
+
for (let i = data.length; i < ssdSaved.length; ++i) {
|
|
203
|
+
expect(ssdSaved[i]).toBe(0);
|
|
204
|
+
}
|
|
205
|
+
});
|
|
206
|
+
it("should have sane tracks", () => {
|
|
207
|
+
const disc = new Disc(true, new DiscConfig(), "test.ssd");
|
|
208
|
+
loadSsd(disc, data, false);
|
|
209
|
+
const sectors = disc.getTrack(false, 0).findSectors();
|
|
210
|
+
expect(sectors.length).toBe(10);
|
|
211
|
+
for (const sector of sectors) {
|
|
212
|
+
expect(sector.hasHeaderCrcError).toBe(false);
|
|
213
|
+
expect(sector.hasDataCrcError).toBe(false);
|
|
214
|
+
}
|
|
215
|
+
});
|
|
216
|
+
},
|
|
217
|
+
);
|
|
218
|
+
|
|
219
|
+
describe("ADF loader tests", function () {
|
|
220
|
+
it("should load a somewhat blank ADFS disc", () => {
|
|
221
|
+
const data = new Uint8Array(327680);
|
|
222
|
+
const disc = new Disc(true, new DiscConfig(), "test.adf");
|
|
223
|
+
loadAdf(disc, data, true);
|
|
224
|
+
expect(disc.tracksUsed).toBe(40);
|
|
225
|
+
const sectors = disc.getTrack(false, 0).findSectors();
|
|
226
|
+
expect(sectors.length).toBe(16);
|
|
227
|
+
for (const sector of sectors) {
|
|
228
|
+
expect(sector.hasHeaderCrcError).toBe(false);
|
|
229
|
+
expect(sector.hasDataCrcError).toBe(false);
|
|
230
|
+
}
|
|
231
|
+
});
|
|
232
|
+
});
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { describe, it, expect } from "vitest";
|
|
2
|
+
|
|
3
|
+
import { Fifo } from "../../src/utils.js";
|
|
4
|
+
|
|
5
|
+
describe("FIFO tests", function () {
|
|
6
|
+
"use strict";
|
|
7
|
+
it("creates ok", function () {
|
|
8
|
+
new Fifo(16);
|
|
9
|
+
});
|
|
10
|
+
it("works for simple cases", function () {
|
|
11
|
+
const f = new Fifo(16);
|
|
12
|
+
expect(f.size).toBe(0);
|
|
13
|
+
f.put(123);
|
|
14
|
+
expect(f.size).toBe(1);
|
|
15
|
+
expect(f.get()).toBe(123);
|
|
16
|
+
expect(f.size).toBe(0);
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
it("works when full", function () {
|
|
20
|
+
const f = new Fifo(4);
|
|
21
|
+
expect(f.size).toBe(0);
|
|
22
|
+
f.put(123);
|
|
23
|
+
f.put(125);
|
|
24
|
+
f.put(126);
|
|
25
|
+
f.put(127);
|
|
26
|
+
expect(f.size).toBe(4);
|
|
27
|
+
f.put(100);
|
|
28
|
+
expect(f.size).toBe(4);
|
|
29
|
+
expect(f.get()).toBe(123);
|
|
30
|
+
expect(f.get()).toBe(125);
|
|
31
|
+
expect(f.get()).toBe(126);
|
|
32
|
+
expect(f.get()).toBe(127);
|
|
33
|
+
expect(f.size).toBe(0);
|
|
34
|
+
});
|
|
35
|
+
});
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import { describe, it, expect, beforeEach } from "vitest";
|
|
2
|
+
import { GamepadSource } from "../../src/gamepad-source.js";
|
|
3
|
+
|
|
4
|
+
describe("GamepadSource", () => {
|
|
5
|
+
// Mock gamepads with various axis positions
|
|
6
|
+
const mockGamepads = [
|
|
7
|
+
{
|
|
8
|
+
axes: [0.5, -0.5, 0.25, -0.75], // pad1: right, up, slight right, mostly up
|
|
9
|
+
},
|
|
10
|
+
{
|
|
11
|
+
axes: [-0.8, 0.3], // pad2: mostly left, slight down
|
|
12
|
+
},
|
|
13
|
+
];
|
|
14
|
+
|
|
15
|
+
let gamepadSource;
|
|
16
|
+
|
|
17
|
+
beforeEach(() => {
|
|
18
|
+
// Create a fake getGamepads function that returns our mocks
|
|
19
|
+
const getGamepads = () => mockGamepads;
|
|
20
|
+
gamepadSource = new GamepadSource(getGamepads);
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
describe("getValue", () => {
|
|
24
|
+
it("should convert gamepad axis 0 value correctly", () => {
|
|
25
|
+
// First axis of first gamepad is 0.5
|
|
26
|
+
// Formula: Math.floor(((1 - 0.5) / 2) * 0xffff)
|
|
27
|
+
// = Math.floor(0.25 * 0xffff) = 0x3fff
|
|
28
|
+
const value = gamepadSource.getValue(0);
|
|
29
|
+
expect(value).toBe(0x3fff);
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
it("should convert gamepad axis 1 value correctly", () => {
|
|
33
|
+
// Second axis of first gamepad is -0.5
|
|
34
|
+
// Formula: Math.floor(((1 - (-0.5)) / 2) * 0xffff)
|
|
35
|
+
// = Math.floor(0.75 * 0xffff) = 0xbfff
|
|
36
|
+
const value = gamepadSource.getValue(1);
|
|
37
|
+
expect(value).toBe(0xbfff);
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
it("should use second gamepad for channel 2 if available", () => {
|
|
41
|
+
// First axis of second gamepad is -0.8
|
|
42
|
+
// Formula: Math.floor(((1 - (-0.8)) / 2) * 0xffff)
|
|
43
|
+
// = Math.floor(0.9 * 0xffff) ≈ 58981
|
|
44
|
+
const value = gamepadSource.getValue(2);
|
|
45
|
+
expect(value).toBe(58981);
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
it("should use second gamepad for channel 3 if available", () => {
|
|
49
|
+
// Second axis of second gamepad is 0.3
|
|
50
|
+
// Formula: Math.floor(((1 - 0.3) / 2) * 0xffff)
|
|
51
|
+
// = Math.floor(0.35 * 0xffff) ≈ 22937
|
|
52
|
+
const value = gamepadSource.getValue(3);
|
|
53
|
+
expect(value).toBe(22937);
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
it("should return center value (0x8000) when no gamepads are connected", () => {
|
|
57
|
+
// Create a source that returns no gamepads
|
|
58
|
+
const noGamepadsSource = new GamepadSource(() => []);
|
|
59
|
+
expect(noGamepadsSource.getValue(0)).toBe(0x8000);
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
it("should return center value for invalid channel", () => {
|
|
63
|
+
expect(gamepadSource.getValue(4)).toBe(0x8000);
|
|
64
|
+
expect(gamepadSource.getValue(-1)).toBe(0x8000);
|
|
65
|
+
});
|
|
66
|
+
});
|
|
67
|
+
});
|