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,307 @@
|
|
|
1
|
+
import { describe, it, expect, beforeEach, afterEach, vi } from "vitest";
|
|
2
|
+
import { Adc } from "../../src/adc.js";
|
|
3
|
+
import { AnalogueSource } from "../../src/analogue-source.js";
|
|
4
|
+
|
|
5
|
+
// Create a mock gamepad source for testing
|
|
6
|
+
class MockGamepadSource extends AnalogueSource {
|
|
7
|
+
constructor() {
|
|
8
|
+
super();
|
|
9
|
+
this.mockValues = {
|
|
10
|
+
0: 0x3fff, // Channel 0: halfway (0.5) -> 0x3fff
|
|
11
|
+
1: 0xffff, // Channel 1: max (-1.0) -> 0xffff
|
|
12
|
+
2: 0x5fff, // Channel 2: quarter (0.25) -> 0x5fff
|
|
13
|
+
3: 0xdfff, // Channel 3: three quarters (-0.75) -> 0xdfff
|
|
14
|
+
};
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
getValue(channel) {
|
|
18
|
+
return this.mockValues[channel] || 0x8000;
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
describe("ADC", () => {
|
|
23
|
+
// Mock dependencies
|
|
24
|
+
const mockSysvia = {
|
|
25
|
+
setcb1: vi.fn(),
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
const mockScheduler = {
|
|
29
|
+
newTask: vi.fn().mockImplementation((callback) => ({
|
|
30
|
+
schedule: vi.fn(),
|
|
31
|
+
cancel: vi.fn(),
|
|
32
|
+
callback,
|
|
33
|
+
})),
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
let adc;
|
|
37
|
+
let mockTask;
|
|
38
|
+
let mockGamepadSource;
|
|
39
|
+
|
|
40
|
+
beforeEach(() => {
|
|
41
|
+
// Reset mocks
|
|
42
|
+
vi.resetAllMocks();
|
|
43
|
+
|
|
44
|
+
// Create a mock task that stores the callback
|
|
45
|
+
mockTask = {
|
|
46
|
+
schedule: vi.fn(),
|
|
47
|
+
cancel: vi.fn(),
|
|
48
|
+
};
|
|
49
|
+
mockScheduler.newTask.mockReturnValue(mockTask);
|
|
50
|
+
|
|
51
|
+
// Create a fresh ADC instance
|
|
52
|
+
adc = new Adc(mockSysvia, mockScheduler);
|
|
53
|
+
|
|
54
|
+
mockGamepadSource = new MockGamepadSource();
|
|
55
|
+
adc.setChannelSource(0, mockGamepadSource);
|
|
56
|
+
adc.setChannelSource(1, mockGamepadSource);
|
|
57
|
+
adc.setChannelSource(2, mockGamepadSource);
|
|
58
|
+
adc.setChannelSource(3, mockGamepadSource);
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
afterEach(() => {
|
|
62
|
+
vi.resetAllMocks();
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
describe("Initialization", () => {
|
|
66
|
+
it("should initialize with default state", () => {
|
|
67
|
+
expect(adc.status).toBe(0x40);
|
|
68
|
+
expect(adc.low).toBe(0x00);
|
|
69
|
+
expect(adc.high).toBe(0x00);
|
|
70
|
+
expect(mockScheduler.newTask).toHaveBeenCalled();
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
it("should reset to default state", () => {
|
|
74
|
+
// Change state
|
|
75
|
+
adc.status = 0xff;
|
|
76
|
+
adc.low = 0xff;
|
|
77
|
+
adc.high = 0xff;
|
|
78
|
+
|
|
79
|
+
// Reset
|
|
80
|
+
adc.reset();
|
|
81
|
+
|
|
82
|
+
// Check state is back to default
|
|
83
|
+
expect(adc.status).toBe(0x40);
|
|
84
|
+
expect(adc.low).toBe(0x00);
|
|
85
|
+
expect(adc.high).toBe(0x00);
|
|
86
|
+
});
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
describe("Reading registers", () => {
|
|
90
|
+
it("should read status register (addr 0)", () => {
|
|
91
|
+
adc.status = 0x42;
|
|
92
|
+
expect(adc.read(0)).toBe(0x42);
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
it("should read high byte register (addr 1)", () => {
|
|
96
|
+
adc.high = 0x42;
|
|
97
|
+
expect(adc.read(1)).toBe(0x42);
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
it("should read low byte register (addr 2)", () => {
|
|
101
|
+
adc.low = 0x42;
|
|
102
|
+
expect(adc.read(2)).toBe(0x42);
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
it("should return 0x40 for undefined register (addr 3)", () => {
|
|
106
|
+
expect(adc.read(3)).toBe(0x40);
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
it("should correctly mask address when reading", () => {
|
|
110
|
+
adc.status = 0x42;
|
|
111
|
+
adc.high = 0x43;
|
|
112
|
+
adc.low = 0x44;
|
|
113
|
+
|
|
114
|
+
expect(adc.read(4)).toBe(0x42); // 4 & 3 = 0
|
|
115
|
+
expect(adc.read(5)).toBe(0x43); // 5 & 3 = 1
|
|
116
|
+
expect(adc.read(6)).toBe(0x44); // 6 & 3 = 2
|
|
117
|
+
expect(adc.read(7)).toBe(0x40); // 7 & 3 = 3
|
|
118
|
+
});
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
describe("Writing to control register", () => {
|
|
122
|
+
it("should ignore writes to non-control registers", () => {
|
|
123
|
+
// Write to addresses 1, 2, 3
|
|
124
|
+
adc.write(1, 0x42);
|
|
125
|
+
adc.write(2, 0x42);
|
|
126
|
+
adc.write(3, 0x42);
|
|
127
|
+
|
|
128
|
+
// Verify no conversion was started
|
|
129
|
+
expect(mockTask.cancel).not.toHaveBeenCalled();
|
|
130
|
+
expect(mockTask.schedule).not.toHaveBeenCalled();
|
|
131
|
+
expect(mockSysvia.setcb1).not.toHaveBeenCalled();
|
|
132
|
+
});
|
|
133
|
+
|
|
134
|
+
it("should start 8-bit conversion on write to control register", () => {
|
|
135
|
+
adc.write(0, 0x00); // 8-bit conversion (bit 3 not set)
|
|
136
|
+
|
|
137
|
+
expect(mockTask.cancel).toHaveBeenCalled();
|
|
138
|
+
expect(mockTask.schedule).toHaveBeenCalledWith(8000); // 8ms for 8-bit
|
|
139
|
+
expect(adc.status).toBe(0x80); // Busy bit set
|
|
140
|
+
expect(mockSysvia.setcb1).toHaveBeenCalledWith(true);
|
|
141
|
+
});
|
|
142
|
+
|
|
143
|
+
it("should start 10-bit conversion on write to control register", () => {
|
|
144
|
+
adc.write(0, 0x08); // 10-bit conversion (bit 3 set)
|
|
145
|
+
|
|
146
|
+
expect(mockTask.cancel).toHaveBeenCalled();
|
|
147
|
+
expect(mockTask.schedule).toHaveBeenCalledWith(20000); // 20ms for 10-bit
|
|
148
|
+
expect(adc.status).toBe(0x88); // Busy bit set, bit 3 set
|
|
149
|
+
expect(mockSysvia.setcb1).toHaveBeenCalledWith(true);
|
|
150
|
+
});
|
|
151
|
+
|
|
152
|
+
it("should store channel number in status register", () => {
|
|
153
|
+
adc.write(0, 0x02); // Channel 2
|
|
154
|
+
expect(adc.status & 0x0f).toBe(0x02);
|
|
155
|
+
|
|
156
|
+
adc.write(0, 0x03); // Channel 3
|
|
157
|
+
expect(adc.status & 0x0f).toBe(0x03);
|
|
158
|
+
});
|
|
159
|
+
|
|
160
|
+
it("should correctly mask address when writing", () => {
|
|
161
|
+
adc.write(4, 0x01); // 4 & 3 = 0
|
|
162
|
+
expect(adc.status & 0x0f).toBe(0x01);
|
|
163
|
+
|
|
164
|
+
// These should be ignored
|
|
165
|
+
adc.write(5, 0x02); // 5 & 3 = 1
|
|
166
|
+
adc.write(6, 0x03); // 6 & 3 = 2
|
|
167
|
+
adc.write(7, 0x04); // 7 & 3 = 3
|
|
168
|
+
|
|
169
|
+
// Status should still reflect the last valid write
|
|
170
|
+
expect(adc.status & 0x0f).toBe(0x01);
|
|
171
|
+
});
|
|
172
|
+
});
|
|
173
|
+
|
|
174
|
+
describe("Analogue sources", () => {
|
|
175
|
+
it("should set and get a channel source", () => {
|
|
176
|
+
const newSource = new MockGamepadSource();
|
|
177
|
+
const result = adc.setChannelSource(1, newSource);
|
|
178
|
+
expect(result).toBe(true);
|
|
179
|
+
expect(adc.getChannelSource(1)).toBe(newSource);
|
|
180
|
+
});
|
|
181
|
+
|
|
182
|
+
it("should clear a channel source", () => {
|
|
183
|
+
const result = adc.clearChannelSource(2);
|
|
184
|
+
expect(result).toBe(true);
|
|
185
|
+
expect(adc.getChannelSource(2)).toBe(null);
|
|
186
|
+
});
|
|
187
|
+
|
|
188
|
+
it("should clear all sources", () => {
|
|
189
|
+
adc.clearSources();
|
|
190
|
+
for (let i = 0; i < 4; i++) {
|
|
191
|
+
expect(adc.getChannelSource(i)).toBe(null);
|
|
192
|
+
}
|
|
193
|
+
});
|
|
194
|
+
});
|
|
195
|
+
|
|
196
|
+
describe("Conversion completion", () => {
|
|
197
|
+
it("should handle completion with no source for the channel", () => {
|
|
198
|
+
adc.clearChannelSource(1);
|
|
199
|
+
|
|
200
|
+
// Set up a conversion for channel 1
|
|
201
|
+
adc.write(0, 0x01);
|
|
202
|
+
|
|
203
|
+
// Reset mock to clearly see what happens during completion
|
|
204
|
+
mockSysvia.setcb1.mockReset();
|
|
205
|
+
|
|
206
|
+
// Simulate conversion completion
|
|
207
|
+
adc.onComplete();
|
|
208
|
+
|
|
209
|
+
// Check results
|
|
210
|
+
expect(adc.status & 0x80).toBe(0); // Busy bit cleared
|
|
211
|
+
expect(adc.status & 0x40).toBe(0x40); // End of conversion bit set
|
|
212
|
+
expect(adc.low).toBe(0); // Default value low byte
|
|
213
|
+
expect(adc.high).toBe(0x80); // Default value high byte (0x8000 >> 8)
|
|
214
|
+
expect(mockSysvia.setcb1).toHaveBeenCalledWith(false); // Interrupt cleared
|
|
215
|
+
});
|
|
216
|
+
|
|
217
|
+
it("should read from source on channel 0", () => {
|
|
218
|
+
// Set channel 0
|
|
219
|
+
adc.write(0, 0x00);
|
|
220
|
+
|
|
221
|
+
// Simulate conversion completion
|
|
222
|
+
adc.onComplete();
|
|
223
|
+
|
|
224
|
+
// Check that the mock value was used
|
|
225
|
+
const expectedValue = mockGamepadSource.getValue(0);
|
|
226
|
+
expect(adc.low).toBe(expectedValue & 0xff);
|
|
227
|
+
expect(adc.high).toBe((expectedValue >>> 8) & 0xff);
|
|
228
|
+
});
|
|
229
|
+
|
|
230
|
+
it("should read from source on channel 1", () => {
|
|
231
|
+
// Set channel 1
|
|
232
|
+
adc.write(0, 0x01);
|
|
233
|
+
|
|
234
|
+
// Simulate conversion completion
|
|
235
|
+
adc.onComplete();
|
|
236
|
+
|
|
237
|
+
// Check that the mock value was used
|
|
238
|
+
const expectedValue = mockGamepadSource.getValue(1);
|
|
239
|
+
expect(adc.low).toBe(expectedValue & 0xff);
|
|
240
|
+
expect(adc.high).toBe((expectedValue >>> 8) & 0xff);
|
|
241
|
+
});
|
|
242
|
+
|
|
243
|
+
it("should use specific source for each channel", () => {
|
|
244
|
+
// Create a special source for channel 2 with a different value
|
|
245
|
+
const specialSource = new MockGamepadSource();
|
|
246
|
+
specialSource.mockValues[2] = 0x1234; // Different value
|
|
247
|
+
|
|
248
|
+
// Set it as the source for channel 2
|
|
249
|
+
adc.setChannelSource(2, specialSource);
|
|
250
|
+
|
|
251
|
+
// Set channel 2
|
|
252
|
+
adc.write(0, 0x02);
|
|
253
|
+
|
|
254
|
+
// Simulate conversion completion
|
|
255
|
+
adc.onComplete();
|
|
256
|
+
|
|
257
|
+
// Should use the special source for channel 2
|
|
258
|
+
const expectedValue = specialSource.getValue(2);
|
|
259
|
+
expect(adc.low).toBe(expectedValue & 0xff);
|
|
260
|
+
expect(adc.high).toBe((expectedValue >>> 8) & 0xff);
|
|
261
|
+
});
|
|
262
|
+
|
|
263
|
+
it("should switch between different sources by channel", () => {
|
|
264
|
+
// Create two different sources with different values
|
|
265
|
+
const source1 = new MockGamepadSource();
|
|
266
|
+
source1.mockValues[2] = 0x1111;
|
|
267
|
+
|
|
268
|
+
const source2 = new MockGamepadSource();
|
|
269
|
+
source2.mockValues[3] = 0x2222;
|
|
270
|
+
|
|
271
|
+
// Set them for different channels
|
|
272
|
+
adc.setChannelSource(2, source1);
|
|
273
|
+
adc.setChannelSource(3, source2);
|
|
274
|
+
|
|
275
|
+
// Test channel 2 (should use source1)
|
|
276
|
+
adc.write(0, 0x02);
|
|
277
|
+
adc.onComplete();
|
|
278
|
+
expect(adc.low).toBe(0x11); // 0x1111 & 0xff
|
|
279
|
+
expect(adc.high).toBe(0x11); // (0x1111 >>> 8) & 0xff
|
|
280
|
+
|
|
281
|
+
// Test channel 3 (should use source2)
|
|
282
|
+
adc.write(0, 0x03);
|
|
283
|
+
adc.onComplete();
|
|
284
|
+
expect(adc.low).toBe(0x22); // 0x2222 & 0xff
|
|
285
|
+
expect(adc.high).toBe(0x22); // (0x2222 >>> 8) & 0xff
|
|
286
|
+
});
|
|
287
|
+
|
|
288
|
+
it("should update status bits correctly after conversion", () => {
|
|
289
|
+
// Set channel 2
|
|
290
|
+
adc.write(0, 0x02);
|
|
291
|
+
|
|
292
|
+
// Simulate conversion completion
|
|
293
|
+
adc.onComplete();
|
|
294
|
+
|
|
295
|
+
// Get the expected value from our mock source
|
|
296
|
+
const expectedValue = mockGamepadSource.getValue(2);
|
|
297
|
+
|
|
298
|
+
// The status should have:
|
|
299
|
+
// - bits 0-3: channel number (0)
|
|
300
|
+
// - bit 6: end of conversion bit (1)
|
|
301
|
+
// - bits 4-5: ((0x5fff >>> 10) & 0x03) = 0x01 (shifted to bit position)
|
|
302
|
+
const expectedStatus = (0x00 & 0x0f) | 0x40 | ((expectedValue >>> 10) & 0x03);
|
|
303
|
+
|
|
304
|
+
expect(adc.status).toBe(expectedStatus);
|
|
305
|
+
});
|
|
306
|
+
});
|
|
307
|
+
});
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { describe, it, expect } from "vitest";
|
|
2
|
+
|
|
3
|
+
import { fake65C12 } from "../../src/fake6502.js";
|
|
4
|
+
|
|
5
|
+
const cpu = fake65C12();
|
|
6
|
+
|
|
7
|
+
describe("BCD tests", function () {
|
|
8
|
+
"use strict";
|
|
9
|
+
it("handles 65c12sbc1", async function () {
|
|
10
|
+
await cpu.initialise();
|
|
11
|
+
cpu.p.reset();
|
|
12
|
+
cpu.p.d = true;
|
|
13
|
+
cpu.a = 0x90;
|
|
14
|
+
cpu.sbc(0x0b);
|
|
15
|
+
expect(cpu.p.v).toBe(false);
|
|
16
|
+
expect(cpu.p.c).toBe(true);
|
|
17
|
+
expect(cpu.a).toBe(126);
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
it("handles 65c12sbc2", async function () {
|
|
21
|
+
await cpu.initialise();
|
|
22
|
+
cpu.p.reset();
|
|
23
|
+
cpu.p.d = true;
|
|
24
|
+
cpu.a = 0x80;
|
|
25
|
+
cpu.sbc(0x01);
|
|
26
|
+
expect(cpu.p.v).toBe(true);
|
|
27
|
+
expect(cpu.p.c).toBe(true);
|
|
28
|
+
expect(cpu.a).toBe(120);
|
|
29
|
+
});
|
|
30
|
+
});
|
|
@@ -0,0 +1,266 @@
|
|
|
1
|
+
import { describe, it, expect, beforeEach, afterEach, vi } from "vitest";
|
|
2
|
+
import { Cmos } from "../../src/cmos.js";
|
|
3
|
+
|
|
4
|
+
describe("CMOS", () => {
|
|
5
|
+
// Mock persistence
|
|
6
|
+
const mockPersistence = {
|
|
7
|
+
load: vi.fn().mockReturnValue(null),
|
|
8
|
+
save: vi.fn(),
|
|
9
|
+
};
|
|
10
|
+
|
|
11
|
+
// Test date (2023-04-15T12:34:56)
|
|
12
|
+
const TEST_DATE = new Date(2023, 3, 15, 12, 34, 56);
|
|
13
|
+
|
|
14
|
+
// CMOS register addresses (from BBC Micro documentation)
|
|
15
|
+
const CMOS_ADDR = {
|
|
16
|
+
SECONDS: 0,
|
|
17
|
+
MINUTES: 2,
|
|
18
|
+
HOURS: 4,
|
|
19
|
+
DAY_OF_WEEK: 6,
|
|
20
|
+
DAY_OF_MONTH: 7,
|
|
21
|
+
MONTH: 8,
|
|
22
|
+
YEAR: 9,
|
|
23
|
+
// Non-RTC addresses for testing
|
|
24
|
+
CONFIG_1: 12,
|
|
25
|
+
CONFIG_2: 13,
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
// Constants from the hardware implementation
|
|
29
|
+
const PORT_B_ENABLE = 0x40; // Bit 6 of port B
|
|
30
|
+
const PORT_B_ADDR_SEL = 0x80; // Bit 7 of port B
|
|
31
|
+
const IC32_READ = 2; // Bit 1 of IC32
|
|
32
|
+
const IC32_DATA_SEL = 4; // Bit 2 of IC32
|
|
33
|
+
|
|
34
|
+
let cmos;
|
|
35
|
+
|
|
36
|
+
beforeEach(() => {
|
|
37
|
+
// Use fake timers for consistent date/time testing
|
|
38
|
+
vi.useFakeTimers();
|
|
39
|
+
vi.setSystemTime(TEST_DATE);
|
|
40
|
+
|
|
41
|
+
// Create a fresh CMOS instance for each test
|
|
42
|
+
cmos = new Cmos(mockPersistence);
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
afterEach(() => {
|
|
46
|
+
vi.useRealTimers();
|
|
47
|
+
vi.resetAllMocks();
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
describe("Initialization", () => {
|
|
51
|
+
it("should initialize with persistence and save default values", () => {
|
|
52
|
+
expect(mockPersistence.save).toHaveBeenCalled();
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
it("should use custom persistence data if available", () => {
|
|
56
|
+
const customData = Array(48).fill(0x42);
|
|
57
|
+
mockPersistence.load.mockReturnValueOnce(customData);
|
|
58
|
+
|
|
59
|
+
const customCmos = new Cmos(mockPersistence);
|
|
60
|
+
|
|
61
|
+
// Reading from a non-RTC location should return our custom data
|
|
62
|
+
// First enable CMOS and set it up for reading
|
|
63
|
+
customCmos.writeControl(PORT_B_ENABLE | PORT_B_ADDR_SEL, CMOS_ADDR.CONFIG_1, 0);
|
|
64
|
+
customCmos.writeControl(PORT_B_ENABLE, CMOS_ADDR.CONFIG_1, 0);
|
|
65
|
+
customCmos.writeControl(PORT_B_ENABLE, 0, IC32_READ | IC32_DATA_SEL);
|
|
66
|
+
|
|
67
|
+
expect(customCmos.read()).toBe(0x42);
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
it("should apply CMOS override when provided", () => {
|
|
71
|
+
const cmosOverride = (store) => {
|
|
72
|
+
const newStore = [...store];
|
|
73
|
+
newStore[CMOS_ADDR.CONFIG_1] = 0x42;
|
|
74
|
+
return newStore;
|
|
75
|
+
};
|
|
76
|
+
|
|
77
|
+
const customCmos = new Cmos(mockPersistence, cmosOverride);
|
|
78
|
+
|
|
79
|
+
// Reading from the overridden location should return our custom value
|
|
80
|
+
// First enable CMOS and set it up for reading
|
|
81
|
+
customCmos.writeControl(PORT_B_ENABLE | PORT_B_ADDR_SEL, CMOS_ADDR.CONFIG_1, 0);
|
|
82
|
+
customCmos.writeControl(PORT_B_ENABLE, CMOS_ADDR.CONFIG_1, 0);
|
|
83
|
+
customCmos.writeControl(PORT_B_ENABLE, 0, IC32_READ | IC32_DATA_SEL);
|
|
84
|
+
|
|
85
|
+
expect(customCmos.read()).toBe(0x42);
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
it("should apply econet settings when provided", () => {
|
|
89
|
+
const econet = { stationId: 0x42 };
|
|
90
|
+
const customCmos = new Cmos(mockPersistence, null, econet);
|
|
91
|
+
|
|
92
|
+
// First read econet station ID (at address 0x0E)
|
|
93
|
+
customCmos.writeControl(PORT_B_ENABLE | PORT_B_ADDR_SEL, 0x0e, 0);
|
|
94
|
+
customCmos.writeControl(PORT_B_ENABLE, 0x0e, 0);
|
|
95
|
+
customCmos.writeControl(PORT_B_ENABLE, 0, IC32_READ | IC32_DATA_SEL);
|
|
96
|
+
|
|
97
|
+
expect(customCmos.read()).toBe(0x42);
|
|
98
|
+
|
|
99
|
+
// Then read FS ID (at address 0x0F)
|
|
100
|
+
customCmos.writeControl(PORT_B_ENABLE | PORT_B_ADDR_SEL, 0x0f, 0);
|
|
101
|
+
customCmos.writeControl(PORT_B_ENABLE, 0x0f, 0);
|
|
102
|
+
customCmos.writeControl(PORT_B_ENABLE, 0, IC32_READ | IC32_DATA_SEL);
|
|
103
|
+
|
|
104
|
+
expect(customCmos.read()).toBe(254);
|
|
105
|
+
});
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
describe("Reading and Writing non-RTC data", () => {
|
|
109
|
+
it("should return 0xFF when CMOS is disabled", () => {
|
|
110
|
+
// Don't enable CMOS (no PORT_B_ENABLE bit)
|
|
111
|
+
expect(cmos.read()).toBe(0xff);
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
it("should write and read from CMOS memory locations", () => {
|
|
115
|
+
// Set address to CONFIG_1 (addr 12)
|
|
116
|
+
cmos.writeControl(PORT_B_ENABLE | PORT_B_ADDR_SEL, CMOS_ADDR.CONFIG_1, 0);
|
|
117
|
+
cmos.writeControl(PORT_B_ENABLE, CMOS_ADDR.CONFIG_1, 0);
|
|
118
|
+
|
|
119
|
+
// Write value 0x42 to CONFIG_1
|
|
120
|
+
cmos.writeControl(PORT_B_ENABLE, 0x42, IC32_DATA_SEL);
|
|
121
|
+
cmos.writeControl(PORT_B_ENABLE, 0x42, 0);
|
|
122
|
+
|
|
123
|
+
// Read it back
|
|
124
|
+
cmos.writeControl(PORT_B_ENABLE, 0, IC32_READ | IC32_DATA_SEL);
|
|
125
|
+
expect(cmos.read()).toBe(0x42);
|
|
126
|
+
|
|
127
|
+
// Check persistence was called
|
|
128
|
+
expect(mockPersistence.save).toHaveBeenCalled();
|
|
129
|
+
});
|
|
130
|
+
|
|
131
|
+
it("should only read when properly configured", () => {
|
|
132
|
+
// Set address to CONFIG_2 (different than other tests)
|
|
133
|
+
cmos.writeControl(PORT_B_ENABLE | PORT_B_ADDR_SEL, CMOS_ADDR.CONFIG_2, 0);
|
|
134
|
+
cmos.writeControl(PORT_B_ENABLE, CMOS_ADDR.CONFIG_2, 0);
|
|
135
|
+
|
|
136
|
+
// Write a known test value
|
|
137
|
+
cmos.writeControl(PORT_B_ENABLE, 0x42, IC32_DATA_SEL);
|
|
138
|
+
cmos.writeControl(PORT_B_ENABLE, 0x42, 0);
|
|
139
|
+
|
|
140
|
+
// Without setting the read mode, should return 0xFF
|
|
141
|
+
expect(cmos.read()).toBe(0xff);
|
|
142
|
+
|
|
143
|
+
// With address select high, should return 0xFF
|
|
144
|
+
cmos.writeControl(PORT_B_ENABLE | PORT_B_ADDR_SEL, 0, IC32_READ);
|
|
145
|
+
expect(cmos.read()).toBe(0xff);
|
|
146
|
+
|
|
147
|
+
// With data select low, should return 0xFF
|
|
148
|
+
cmos.writeControl(PORT_B_ENABLE, 0, IC32_READ);
|
|
149
|
+
expect(cmos.read()).toBe(0xff);
|
|
150
|
+
|
|
151
|
+
// Make sure we're still pointing at the right address
|
|
152
|
+
cmos.writeControl(PORT_B_ENABLE | PORT_B_ADDR_SEL, CMOS_ADDR.CONFIG_2, 0);
|
|
153
|
+
cmos.writeControl(PORT_B_ENABLE, CMOS_ADDR.CONFIG_2, 0);
|
|
154
|
+
|
|
155
|
+
// With everything set correctly, should return the value
|
|
156
|
+
cmos.writeControl(PORT_B_ENABLE, 0, IC32_READ | IC32_DATA_SEL);
|
|
157
|
+
expect(cmos.read()).toBe(0x42);
|
|
158
|
+
});
|
|
159
|
+
});
|
|
160
|
+
|
|
161
|
+
describe("Reading RTC values", () => {
|
|
162
|
+
// Helper function to read a specific RTC register
|
|
163
|
+
function readRtcRegister(register) {
|
|
164
|
+
// Set address
|
|
165
|
+
cmos.writeControl(PORT_B_ENABLE | PORT_B_ADDR_SEL, register, 0);
|
|
166
|
+
cmos.writeControl(PORT_B_ENABLE, register, 0);
|
|
167
|
+
|
|
168
|
+
// Configure for reading
|
|
169
|
+
cmos.writeControl(PORT_B_ENABLE, 0, IC32_READ | IC32_DATA_SEL);
|
|
170
|
+
|
|
171
|
+
return cmos.read();
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
it("should read current time from RTC registers", () => {
|
|
175
|
+
// Helper function for BCD conversion (same as in cmos.js)
|
|
176
|
+
function toBcd(value) {
|
|
177
|
+
return parseInt(value.toString(10), 16);
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
// Test all RTC components
|
|
181
|
+
expect(readRtcRegister(CMOS_ADDR.SECONDS)).toBe(toBcd(TEST_DATE.getSeconds()));
|
|
182
|
+
expect(readRtcRegister(CMOS_ADDR.MINUTES)).toBe(toBcd(TEST_DATE.getMinutes()));
|
|
183
|
+
expect(readRtcRegister(CMOS_ADDR.HOURS)).toBe(toBcd(TEST_DATE.getHours()));
|
|
184
|
+
expect(readRtcRegister(CMOS_ADDR.DAY_OF_WEEK)).toBe(toBcd(TEST_DATE.getDay() + 1));
|
|
185
|
+
expect(readRtcRegister(CMOS_ADDR.DAY_OF_MONTH)).toBe(toBcd(TEST_DATE.getDate()));
|
|
186
|
+
expect(readRtcRegister(CMOS_ADDR.MONTH)).toBe(toBcd(TEST_DATE.getMonth() + 1));
|
|
187
|
+
});
|
|
188
|
+
});
|
|
189
|
+
|
|
190
|
+
describe("Setting RTC values", () => {
|
|
191
|
+
// Helper to read a specific RTC register
|
|
192
|
+
function readRtcRegister(register) {
|
|
193
|
+
cmos.writeControl(PORT_B_ENABLE | PORT_B_ADDR_SEL, register, 0);
|
|
194
|
+
cmos.writeControl(PORT_B_ENABLE, register, 0);
|
|
195
|
+
cmos.writeControl(PORT_B_ENABLE, 0, IC32_READ | IC32_DATA_SEL);
|
|
196
|
+
return cmos.read();
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
// Helper to write to a specific RTC register
|
|
200
|
+
function writeRtcRegister(register, value) {
|
|
201
|
+
cmos.writeControl(PORT_B_ENABLE | PORT_B_ADDR_SEL, register, 0);
|
|
202
|
+
cmos.writeControl(PORT_B_ENABLE, register, 0);
|
|
203
|
+
cmos.writeControl(PORT_B_ENABLE, value, IC32_DATA_SEL);
|
|
204
|
+
cmos.writeControl(PORT_B_ENABLE, value, 0);
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
it("should update RTC values when written", () => {
|
|
208
|
+
// Set hours to 10
|
|
209
|
+
writeRtcRegister(CMOS_ADDR.HOURS, 0x10);
|
|
210
|
+
|
|
211
|
+
// Advance time slightly to ensure changes take effect
|
|
212
|
+
vi.advanceTimersByTime(100);
|
|
213
|
+
|
|
214
|
+
// Read back hours
|
|
215
|
+
expect(readRtcRegister(CMOS_ADDR.HOURS)).toBe(0x10);
|
|
216
|
+
|
|
217
|
+
// Set minutes to 45
|
|
218
|
+
writeRtcRegister(CMOS_ADDR.MINUTES, 0x45);
|
|
219
|
+
|
|
220
|
+
// Advance time slightly
|
|
221
|
+
vi.advanceTimersByTime(100);
|
|
222
|
+
|
|
223
|
+
// Read back minutes
|
|
224
|
+
expect(readRtcRegister(CMOS_ADDR.MINUTES)).toBe(0x45);
|
|
225
|
+
});
|
|
226
|
+
});
|
|
227
|
+
|
|
228
|
+
describe("BCD Conversion Logic", () => {
|
|
229
|
+
it("should correctly convert between decimal and BCD", () => {
|
|
230
|
+
// Helper functions for BCD conversion (same as in cmos.js)
|
|
231
|
+
const toBcd = (value) => parseInt(value.toString(10), 16);
|
|
232
|
+
const fromBcd = (value) => parseInt(value.toString(16), 10);
|
|
233
|
+
|
|
234
|
+
// Test toBcd conversion
|
|
235
|
+
expect(toBcd(0)).toBe(0x00);
|
|
236
|
+
expect(toBcd(9)).toBe(0x09);
|
|
237
|
+
expect(toBcd(10)).toBe(0x10);
|
|
238
|
+
expect(toBcd(42)).toBe(0x42);
|
|
239
|
+
expect(toBcd(99)).toBe(0x99);
|
|
240
|
+
|
|
241
|
+
// Test fromBcd conversion
|
|
242
|
+
expect(fromBcd(0x00)).toBe(0);
|
|
243
|
+
expect(fromBcd(0x09)).toBe(9);
|
|
244
|
+
expect(fromBcd(0x10)).toBe(10);
|
|
245
|
+
expect(fromBcd(0x42)).toBe(42);
|
|
246
|
+
expect(fromBcd(0x99)).toBe(99);
|
|
247
|
+
|
|
248
|
+
// Test round-trips
|
|
249
|
+
for (let i = 0; i < 100; i++) {
|
|
250
|
+
expect(fromBcd(toBcd(i))).toBe(i);
|
|
251
|
+
}
|
|
252
|
+
});
|
|
253
|
+
|
|
254
|
+
it("should handle year century threshold correctly", () => {
|
|
255
|
+
const fromBcd = (value) => parseInt(value.toString(16), 10);
|
|
256
|
+
|
|
257
|
+
// Years 80-99 should use 1900 as base
|
|
258
|
+
expect(fromBcd(0x80) >= 80 ? 1900 : 2000).toBe(1900);
|
|
259
|
+
expect(fromBcd(0x99) >= 80 ? 1900 : 2000).toBe(1900);
|
|
260
|
+
|
|
261
|
+
// Years 00-79 should use 2000 as base
|
|
262
|
+
expect(fromBcd(0x00) >= 80 ? 1900 : 2000).toBe(2000);
|
|
263
|
+
expect(fromBcd(0x79) >= 80 ? 1900 : 2000).toBe(2000);
|
|
264
|
+
});
|
|
265
|
+
});
|
|
266
|
+
});
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
import { describe, it, expect } from "vitest";
|
|
2
|
+
|
|
3
|
+
import { Disc, IbmDiscFormat } from "../../src/disc.js";
|
|
4
|
+
import { DiscDrive } from "../../src/disc-drive.js";
|
|
5
|
+
import { Scheduler } from "../../src/scheduler.js";
|
|
6
|
+
|
|
7
|
+
describe("Disc drive tests", function () {
|
|
8
|
+
it("starts empty", () => {
|
|
9
|
+
const scheduler = new Scheduler();
|
|
10
|
+
const drive = new DiscDrive(0, scheduler);
|
|
11
|
+
expect(drive.trackLength).toBe(IbmDiscFormat.bytesPerTrack);
|
|
12
|
+
expect(drive.disc).toBeFalsy();
|
|
13
|
+
expect(drive.spinning).toBe(false);
|
|
14
|
+
drive.setPulsesCallback(() => {
|
|
15
|
+
expect.fail("no callbacks expected");
|
|
16
|
+
});
|
|
17
|
+
scheduler.polltime(1000000);
|
|
18
|
+
});
|
|
19
|
+
it("sets a disc", () => {
|
|
20
|
+
const scheduler = new Scheduler();
|
|
21
|
+
const drive = new DiscDrive(0, scheduler);
|
|
22
|
+
const disc = Disc.createBlank();
|
|
23
|
+
drive.setDisc(disc);
|
|
24
|
+
expect(drive.disc).toBe(disc);
|
|
25
|
+
});
|
|
26
|
+
it("calls back with pulses after spinning starts", () => {
|
|
27
|
+
const scheduler = new Scheduler();
|
|
28
|
+
const drive = new DiscDrive(0, scheduler);
|
|
29
|
+
drive.setDisc(0, Disc.createBlank());
|
|
30
|
+
drive.setPulsesCallback(() => {
|
|
31
|
+
expect.fail("no callbacks expected");
|
|
32
|
+
});
|
|
33
|
+
scheduler.polltime(1000000);
|
|
34
|
+
drive.startSpinning();
|
|
35
|
+
let numPulses = 0;
|
|
36
|
+
drive.setPulsesCallback(() => numPulses++);
|
|
37
|
+
scheduler.polltime(500);
|
|
38
|
+
expect(numPulses).toBe(4);
|
|
39
|
+
drive.stopSpinning();
|
|
40
|
+
drive.setPulsesCallback(() => {
|
|
41
|
+
expect.fail("no callbacks expected");
|
|
42
|
+
});
|
|
43
|
+
scheduler.polltime(1000000);
|
|
44
|
+
});
|
|
45
|
+
it("generates quasi random pulses with a blank disc", () => {
|
|
46
|
+
const scheduler = new Scheduler();
|
|
47
|
+
const drive = new DiscDrive(0, scheduler);
|
|
48
|
+
drive.setDisc(Disc.createBlank());
|
|
49
|
+
drive.getQuasiRandomPulses = () => {
|
|
50
|
+
return 0xdeadbeef;
|
|
51
|
+
};
|
|
52
|
+
let called = false;
|
|
53
|
+
drive.setPulsesCallback((pulses, numPulses) => {
|
|
54
|
+
called = true;
|
|
55
|
+
expect(numPulses).toBe(32);
|
|
56
|
+
expect(pulses).toBe(0xdeadbeef);
|
|
57
|
+
});
|
|
58
|
+
drive.startSpinning();
|
|
59
|
+
scheduler.polltime(1000000);
|
|
60
|
+
expect(called).toBe(true);
|
|
61
|
+
});
|
|
62
|
+
it("asserts index all the time with no disc", () => {
|
|
63
|
+
const scheduler = new Scheduler();
|
|
64
|
+
const drive = new DiscDrive(0, scheduler);
|
|
65
|
+
expect(drive.indexPulse).toBe(true);
|
|
66
|
+
});
|
|
67
|
+
it("asserts index periodically with a spinning disc", () => {
|
|
68
|
+
const scheduler = new Scheduler();
|
|
69
|
+
const drive = new DiscDrive(0, scheduler);
|
|
70
|
+
drive.setDisc(Disc.createBlank());
|
|
71
|
+
drive.startSpinning();
|
|
72
|
+
let previousIndex = drive.indexPulse;
|
|
73
|
+
let risingEdges = 0;
|
|
74
|
+
const cyclesPerSecond = 2 * 1000 * 1000;
|
|
75
|
+
const cyclesPerIter = cyclesPerSecond / 60;
|
|
76
|
+
const rpm = 300;
|
|
77
|
+
const testSeconds = 5;
|
|
78
|
+
for (let cycle = 0; cycle < testSeconds * cyclesPerSecond; cycle += cyclesPerIter) {
|
|
79
|
+
scheduler.polltime(cyclesPerIter);
|
|
80
|
+
if (drive.indexPulse && !previousIndex) risingEdges++;
|
|
81
|
+
previousIndex = drive.indexPulse;
|
|
82
|
+
}
|
|
83
|
+
expect(risingEdges).toBe((rpm / 60) * testSeconds);
|
|
84
|
+
});
|
|
85
|
+
});
|