jsbeeb 1.1.1 → 1.3.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +25 -12
- package/src/6502.js +51 -41
- package/src/app/app.js +99 -8
- package/src/app/electron.js +47 -5
- package/src/app/preload.js +11 -1
- package/src/config.js +9 -2
- package/src/disc.js +2 -2
- package/src/filestore.js +1 -4
- package/src/gamepad-source.js +1 -1
- package/src/machine-session.js +396 -0
- package/src/main.js +36 -7
- package/src/music5000-worklet.js +1 -0
- package/src/music5000.js +1 -9
- package/src/sth.js +3 -1
- package/src/utils.js +2 -2
- package/src/web/audio-renderer.js +1 -1
- package/src/web/debug.js +2 -2
- package/tests/test-machine.js +82 -1
- package/.editorconfig +0 -15
- package/.git-blame-ignore-revs +0 -3
- package/.github/copilot-instructions.md +0 -94
- package/.github/workflows/claude-issue-triage.yml +0 -105
- package/.github/workflows/claude.yml +0 -63
- package/.github/workflows/release-please.yml +0 -75
- package/.github/workflows/test-and-deploy.yml +0 -86
- package/.gitmodules +0 -6
- package/.husky/pre-commit +0 -1
- package/.idea/codeStyleSettings.xml +0 -9
- package/.idea/codeStyles/Project.xml +0 -62
- package/.idea/codeStyles/codeStyleConfig.xml +0 -5
- package/.idea/compiler.xml +0 -22
- package/.idea/copyright/profiles_settings.xml +0 -3
- package/.idea/encodings.xml +0 -6
- package/.idea/inspectionProfiles/Project_Default.xml +0 -7
- package/.idea/jsLibraryMappings.xml +0 -6
- package/.idea/jsLinters/jshint.xml +0 -85
- package/.idea/jsLinters/jslint.xml +0 -15
- package/.idea/jsbeeb.iml +0 -11
- package/.idea/misc.xml +0 -6
- package/.idea/modules.xml +0 -8
- package/.idea/prettier.xml +0 -7
- package/.idea/runConfigurations/Debug.xml +0 -5
- package/.idea/scopes/scope_settings.xml +0 -5
- package/.idea/vcs.xml +0 -8
- package/.prettierignore +0 -4
- package/.prettierrc.json +0 -1
- package/.release-please-manifest.json +0 -3
- package/.vscode/launch.json +0 -14
- package/.vscode/settings.json +0 -6
- package/CHANGELOG.md +0 -32
- package/CLAUDE.md +0 -136
- package/Dockerfile +0 -22
- package/Makefile +0 -30
- package/docker/nginx-default.conf +0 -10
- package/docs/pal-comb-filter-research.md +0 -129
- package/docs/pal-simulation-design.md +0 -368
- package/eslint.config.js +0 -35
- package/index.html +0 -954
- package/jsconfig.json +0 -10
- package/public/discs/README.Irq-Timing +0 -3
- package/public/discs/README.bcdtest +0 -5
- package/public/discs/README.elite +0 -6
- package/public/discs/README.eng_test +0 -3
- package/public/discs/README.protection +0 -7
- package/public/favicon.ico +0 -0
- package/public/images/botbar.png +0 -0
- package/public/images/cub-monitor.png +0 -0
- package/public/images/jsbeeb-example.png +0 -0
- package/public/images/placeholder.png +0 -0
- package/public/images/red-off-16.png +0 -0
- package/public/images/red-on-16.png +0 -0
- package/public/images/sb/CD-left.jpg +0 -0
- package/public/images/sb/CD-right.jpg +0 -0
- package/public/images/sidebar.png +0 -0
- package/public/images/tv.png +0 -0
- package/public/images/yellow-off-16.png +0 -0
- package/public/images/yellow-on-16.png +0 -0
- package/public/jsbeeb-icon.png +0 -0
- package/public/robots.txt +0 -3
- package/public/roms/ADFS1-53.rom +0 -0
- package/public/roms/BASIC.ROM +0 -0
- package/public/roms/README +0 -4
- package/public/roms/a01/BASIC1.rom +0 -0
- package/public/roms/ample.rom +0 -0
- package/public/roms/ats-3.0.rom +0 -0
- package/public/roms/b/DFS-0.9.rom +0 -0
- package/public/roms/b/DFS-1.2.rom +0 -0
- package/public/roms/b1770/dfs1770.rom +0 -0
- package/public/roms/b1770/zADFS.ROM +0 -0
- package/public/roms/bp/dfs.rom +0 -0
- package/public/roms/bp/zADFS.ROM +0 -0
- package/public/roms/bpos.rom +0 -0
- package/public/roms/compact/adfs210.rom +0 -0
- package/public/roms/compact/basic48.rom +0 -0
- package/public/roms/compact/basic486.rom +0 -0
- package/public/roms/compact/os51.rom +0 -0
- package/public/roms/compact/utils.rom +0 -0
- package/public/roms/deos.rom +0 -0
- package/public/roms/master/anfs-4.25.rom +0 -0
- package/public/roms/master/mos.txt +0 -68819
- package/public/roms/master/mos3.20 +0 -0
- package/public/roms/os.rom +0 -0
- package/public/roms/os01.rom +0 -0
- package/public/roms/tube/6502Tube.rom +0 -0
- package/public/roms/tube/ARMeval_100.rom +0 -0
- package/public/roms/tube/BIOS.ROM +0 -0
- package/public/roms/tube/ReCo6502ROM_816 +0 -0
- package/public/roms/tube/Z80_120.rom +0 -0
- package/public/roms/us/USBASIC.rom +0 -0
- package/public/roms/us/USDNFS.rom +0 -0
- package/public/roms/usmos.rom +0 -0
- package/public/sounds/disc525/motor.wav +0 -0
- package/public/sounds/disc525/motoroff.wav +0 -0
- package/public/sounds/disc525/motoron.wav +0 -0
- package/public/sounds/disc525/seek.wav +0 -0
- package/public/sounds/disc525/seek2.wav +0 -0
- package/public/sounds/disc525/seek3.wav +0 -0
- package/public/sounds/disc525/step.wav +0 -0
- package/public/teletext/txt0.dat +0 -0
- package/public/teletext/txt1.dat +0 -0
- package/public/teletext/txt2.dat +0 -0
- package/public/teletext/txt3.dat +0 -0
- package/release-please-config.json +0 -13
- package/run-container.sh +0 -92
- package/tests/integration/RmwX.asm +0 -47
- package/tests/integration/TestInstructionsSource +0 -27
- package/tests/integration/TestTimingsResults +0 -27
- package/tests/integration/TestTimingsSource +0 -61
- package/tests/integration/bcd.js +0 -23
- package/tests/integration/dormann.js +0 -101
- package/tests/integration/dp111timing.js +0 -42
- package/tests/integration/ensure-submodules.js +0 -25
- package/tests/integration/nops.bas +0 -119
- package/tests/integration/nops.js +0 -24
- package/tests/integration/protection.js +0 -26
- package/tests/integration/rmw.js +0 -69
- package/tests/integration/teletext/expected_bug_469.png +0 -0
- package/tests/integration/teletext/expected_flash_0.png +0 -0
- package/tests/integration/teletext/expected_flash_1.png +0 -0
- package/tests/integration/teletext/expected_hoglet_held_char.png +0 -0
- package/tests/integration/teletext/expected_reveal_flash_0.png +0 -0
- package/tests/integration/teletext/expected_reveal_flash_1.png +0 -0
- package/tests/integration/teletext.js +0 -126
- package/tests/integration/timings.js +0 -56
- package/tests/integration/via.js +0 -1125
- package/tests/suite/README.md +0 -7
- package/tests/suite/Test Suite 2.15.txt +0 -373
- package/tests/suite/bin/ start +0 -0
- package/tests/suite/bin/adca +0 -0
- package/tests/suite/bin/adcax +0 -0
- package/tests/suite/bin/adcay +0 -0
- package/tests/suite/bin/adcb +0 -0
- package/tests/suite/bin/adcix +0 -0
- package/tests/suite/bin/adciy +0 -0
- package/tests/suite/bin/adcz +0 -0
- package/tests/suite/bin/adczx +0 -0
- package/tests/suite/bin/alrb +0 -0
- package/tests/suite/bin/ancb +0 -0
- package/tests/suite/bin/anda +0 -0
- package/tests/suite/bin/andax +0 -0
- package/tests/suite/bin/anday +0 -0
- package/tests/suite/bin/andb +0 -0
- package/tests/suite/bin/andix +0 -0
- package/tests/suite/bin/andiy +0 -0
- package/tests/suite/bin/andz +0 -0
- package/tests/suite/bin/andzx +0 -0
- package/tests/suite/bin/aneb +0 -0
- package/tests/suite/bin/arrb +0 -0
- package/tests/suite/bin/asla +0 -0
- package/tests/suite/bin/aslax +0 -0
- package/tests/suite/bin/asln +0 -0
- package/tests/suite/bin/aslz +0 -0
- package/tests/suite/bin/aslzx +0 -0
- package/tests/suite/bin/asoa +0 -0
- package/tests/suite/bin/asoax +0 -0
- package/tests/suite/bin/asoay +0 -0
- package/tests/suite/bin/asoix +0 -0
- package/tests/suite/bin/asoiy +0 -0
- package/tests/suite/bin/asoz +0 -0
- package/tests/suite/bin/asozx +0 -0
- package/tests/suite/bin/axsa +0 -0
- package/tests/suite/bin/axsix +0 -0
- package/tests/suite/bin/axsz +0 -0
- package/tests/suite/bin/axszy +0 -0
- package/tests/suite/bin/bccr +0 -0
- package/tests/suite/bin/bcsr +0 -0
- package/tests/suite/bin/beqr +0 -0
- package/tests/suite/bin/bita +0 -0
- package/tests/suite/bin/bitz +0 -0
- package/tests/suite/bin/bmir +0 -0
- package/tests/suite/bin/bner +0 -0
- package/tests/suite/bin/bplr +0 -0
- package/tests/suite/bin/branchwrap +0 -0
- package/tests/suite/bin/brkn +0 -0
- package/tests/suite/bin/bvcr +0 -0
- package/tests/suite/bin/bvsr +0 -0
- package/tests/suite/bin/cia1pb6 +0 -0
- package/tests/suite/bin/cia1pb7 +0 -0
- package/tests/suite/bin/cia1ta +0 -0
- package/tests/suite/bin/cia1tab +0 -0
- package/tests/suite/bin/cia1tb +0 -0
- package/tests/suite/bin/cia1tb123 +0 -0
- package/tests/suite/bin/cia2pb6 +0 -0
- package/tests/suite/bin/cia2pb7 +0 -0
- package/tests/suite/bin/cia2ta +0 -0
- package/tests/suite/bin/cia2tb +0 -0
- package/tests/suite/bin/cia2tb123 +0 -0
- package/tests/suite/bin/clcn +0 -0
- package/tests/suite/bin/cldn +0 -0
- package/tests/suite/bin/clin +0 -0
- package/tests/suite/bin/clvn +0 -0
- package/tests/suite/bin/cmpa +0 -0
- package/tests/suite/bin/cmpax +0 -0
- package/tests/suite/bin/cmpay +0 -0
- package/tests/suite/bin/cmpb +0 -0
- package/tests/suite/bin/cmpix +0 -0
- package/tests/suite/bin/cmpiy +0 -0
- package/tests/suite/bin/cmpz +0 -0
- package/tests/suite/bin/cmpzx +0 -0
- package/tests/suite/bin/cntdef +0 -0
- package/tests/suite/bin/cnto2 +0 -0
- package/tests/suite/bin/cpuport +0 -0
- package/tests/suite/bin/cputiming +0 -0
- package/tests/suite/bin/cpxa +0 -0
- package/tests/suite/bin/cpxb +0 -0
- package/tests/suite/bin/cpxz +0 -0
- package/tests/suite/bin/cpya +0 -0
- package/tests/suite/bin/cpyb +0 -0
- package/tests/suite/bin/cpyz +0 -0
- package/tests/suite/bin/dcma +0 -0
- package/tests/suite/bin/dcmax +0 -0
- package/tests/suite/bin/dcmay +0 -0
- package/tests/suite/bin/dcmix +0 -0
- package/tests/suite/bin/dcmiy +0 -0
- package/tests/suite/bin/dcmz +0 -0
- package/tests/suite/bin/dcmzx +0 -0
- package/tests/suite/bin/deca +0 -0
- package/tests/suite/bin/decax +0 -0
- package/tests/suite/bin/decz +0 -0
- package/tests/suite/bin/deczx +0 -0
- package/tests/suite/bin/dexn +0 -0
- package/tests/suite/bin/deyn +0 -0
- package/tests/suite/bin/eora +0 -0
- package/tests/suite/bin/eorax +0 -0
- package/tests/suite/bin/eoray +0 -0
- package/tests/suite/bin/eorb +0 -0
- package/tests/suite/bin/eorix +0 -0
- package/tests/suite/bin/eoriy +0 -0
- package/tests/suite/bin/eorz +0 -0
- package/tests/suite/bin/eorzx +0 -0
- package/tests/suite/bin/finish +0 -0
- package/tests/suite/bin/flipos +0 -0
- package/tests/suite/bin/icr01 +0 -0
- package/tests/suite/bin/imr +0 -0
- package/tests/suite/bin/inca +0 -0
- package/tests/suite/bin/incax +0 -0
- package/tests/suite/bin/incz +0 -0
- package/tests/suite/bin/inczx +0 -0
- package/tests/suite/bin/insa +0 -0
- package/tests/suite/bin/insax +0 -0
- package/tests/suite/bin/insay +0 -0
- package/tests/suite/bin/insix +0 -0
- package/tests/suite/bin/insiy +0 -0
- package/tests/suite/bin/insz +0 -0
- package/tests/suite/bin/inszx +0 -0
- package/tests/suite/bin/inxn +0 -0
- package/tests/suite/bin/inyn +0 -0
- package/tests/suite/bin/irq +0 -0
- package/tests/suite/bin/jmpi +0 -0
- package/tests/suite/bin/jmpw +0 -0
- package/tests/suite/bin/jsrw +0 -0
- package/tests/suite/bin/lasay +0 -0
- package/tests/suite/bin/laxa +0 -0
- package/tests/suite/bin/laxay +0 -0
- package/tests/suite/bin/laxix +0 -0
- package/tests/suite/bin/laxiy +0 -0
- package/tests/suite/bin/laxz +0 -0
- package/tests/suite/bin/laxzy +0 -0
- package/tests/suite/bin/ldaa +0 -0
- package/tests/suite/bin/ldaax +0 -0
- package/tests/suite/bin/ldaay +0 -0
- package/tests/suite/bin/ldab +0 -0
- package/tests/suite/bin/ldaix +0 -0
- package/tests/suite/bin/ldaiy +0 -0
- package/tests/suite/bin/ldaz +0 -0
- package/tests/suite/bin/ldazx +0 -0
- package/tests/suite/bin/ldxa +0 -0
- package/tests/suite/bin/ldxay +0 -0
- package/tests/suite/bin/ldxb +0 -0
- package/tests/suite/bin/ldxz +0 -0
- package/tests/suite/bin/ldxzy +0 -0
- package/tests/suite/bin/ldya +0 -0
- package/tests/suite/bin/ldyax +0 -0
- package/tests/suite/bin/ldyb +0 -0
- package/tests/suite/bin/ldyz +0 -0
- package/tests/suite/bin/ldyzx +0 -0
- package/tests/suite/bin/loadth +0 -0
- package/tests/suite/bin/lsea +0 -0
- package/tests/suite/bin/lseax +0 -0
- package/tests/suite/bin/lseay +0 -0
- package/tests/suite/bin/lseix +0 -0
- package/tests/suite/bin/lseiy +0 -0
- package/tests/suite/bin/lsez +0 -0
- package/tests/suite/bin/lsezx +0 -0
- package/tests/suite/bin/lsra +0 -0
- package/tests/suite/bin/lsrax +0 -0
- package/tests/suite/bin/lsrn +0 -0
- package/tests/suite/bin/lsrz +0 -0
- package/tests/suite/bin/lsrzx +0 -0
- package/tests/suite/bin/lxab +0 -0
- package/tests/suite/bin/mmu +0 -0
- package/tests/suite/bin/mmufetch +0 -0
- package/tests/suite/bin/nmi +0 -0
- package/tests/suite/bin/nopa +0 -0
- package/tests/suite/bin/nopax +0 -0
- package/tests/suite/bin/nopb +0 -0
- package/tests/suite/bin/nopn +0 -0
- package/tests/suite/bin/nopz +0 -0
- package/tests/suite/bin/nopzx +0 -0
- package/tests/suite/bin/oneshot +0 -0
- package/tests/suite/bin/oraa +0 -0
- package/tests/suite/bin/oraax +0 -0
- package/tests/suite/bin/oraay +0 -0
- package/tests/suite/bin/orab +0 -0
- package/tests/suite/bin/oraix +0 -0
- package/tests/suite/bin/oraiy +0 -0
- package/tests/suite/bin/oraz +0 -0
- package/tests/suite/bin/orazx +0 -0
- package/tests/suite/bin/phan +0 -0
- package/tests/suite/bin/phpn +0 -0
- package/tests/suite/bin/plan +0 -0
- package/tests/suite/bin/plpn +0 -0
- package/tests/suite/bin/rlaa +0 -0
- package/tests/suite/bin/rlaax +0 -0
- package/tests/suite/bin/rlaay +0 -0
- package/tests/suite/bin/rlaix +0 -0
- package/tests/suite/bin/rlaiy +0 -0
- package/tests/suite/bin/rlaz +0 -0
- package/tests/suite/bin/rlazx +0 -0
- package/tests/suite/bin/rola +0 -0
- package/tests/suite/bin/rolax +0 -0
- package/tests/suite/bin/roln +0 -0
- package/tests/suite/bin/rolz +0 -0
- package/tests/suite/bin/rolzx +0 -0
- package/tests/suite/bin/rora +0 -0
- package/tests/suite/bin/rorax +0 -0
- package/tests/suite/bin/rorn +0 -0
- package/tests/suite/bin/rorz +0 -0
- package/tests/suite/bin/rorzx +0 -0
- package/tests/suite/bin/rraa +0 -0
- package/tests/suite/bin/rraax +0 -0
- package/tests/suite/bin/rraay +0 -0
- package/tests/suite/bin/rraix +0 -0
- package/tests/suite/bin/rraiy +0 -0
- package/tests/suite/bin/rraz +0 -0
- package/tests/suite/bin/rrazx +0 -0
- package/tests/suite/bin/rtin +0 -0
- package/tests/suite/bin/rtsn +0 -0
- package/tests/suite/bin/sbca +0 -0
- package/tests/suite/bin/sbcax +0 -0
- package/tests/suite/bin/sbcay +0 -0
- package/tests/suite/bin/sbcb +0 -0
- package/tests/suite/bin/sbcb(eb) +0 -0
- package/tests/suite/bin/sbcix +0 -0
- package/tests/suite/bin/sbciy +0 -0
- package/tests/suite/bin/sbcz +0 -0
- package/tests/suite/bin/sbczx +0 -0
- package/tests/suite/bin/sbxb +0 -0
- package/tests/suite/bin/secn +0 -0
- package/tests/suite/bin/sedn +0 -0
- package/tests/suite/bin/sein +0 -0
- package/tests/suite/bin/shaay +0 -0
- package/tests/suite/bin/shaiy +0 -0
- package/tests/suite/bin/shsay +0 -0
- package/tests/suite/bin/shxay +0 -0
- package/tests/suite/bin/shyax +0 -0
- package/tests/suite/bin/staa +0 -0
- package/tests/suite/bin/staax +0 -0
- package/tests/suite/bin/staay +0 -0
- package/tests/suite/bin/staix +0 -0
- package/tests/suite/bin/staiy +0 -0
- package/tests/suite/bin/staz +0 -0
- package/tests/suite/bin/stazx +0 -0
- package/tests/suite/bin/stxa +0 -0
- package/tests/suite/bin/stxz +0 -0
- package/tests/suite/bin/stxzy +0 -0
- package/tests/suite/bin/stya +0 -0
- package/tests/suite/bin/styz +0 -0
- package/tests/suite/bin/styzx +0 -0
- package/tests/suite/bin/taxn +0 -0
- package/tests/suite/bin/tayn +0 -0
- package/tests/suite/bin/trap1 +0 -0
- package/tests/suite/bin/trap10 +0 -0
- package/tests/suite/bin/trap11 +0 -0
- package/tests/suite/bin/trap12 +0 -0
- package/tests/suite/bin/trap13 +0 -0
- package/tests/suite/bin/trap14 +0 -0
- package/tests/suite/bin/trap15 +0 -0
- package/tests/suite/bin/trap16 +0 -0
- package/tests/suite/bin/trap17 +0 -0
- package/tests/suite/bin/trap2 +0 -0
- package/tests/suite/bin/trap3 +0 -0
- package/tests/suite/bin/trap4 +0 -0
- package/tests/suite/bin/trap5 +0 -0
- package/tests/suite/bin/trap6 +0 -0
- package/tests/suite/bin/trap7 +0 -0
- package/tests/suite/bin/trap8 +0 -0
- package/tests/suite/bin/trap9 +0 -0
- package/tests/suite/bin/tsxn +0 -0
- package/tests/suite/bin/txan +0 -0
- package/tests/suite/bin/txsn +0 -0
- package/tests/suite/bin/tyan +0 -0
- package/tests/suite/cbm-hackers-post.html +0 -178
- package/tests/suite/cbm-hackers-post.md +0 -78
- package/tests/test-suite.js +0 -147
- package/tests/test.css +0 -7
- package/tests/unit/gzip/test-1 +0 -0
- package/tests/unit/gzip/test-1.gz +0 -0
- package/tests/unit/gzip/test-2 +0 -0
- package/tests/unit/gzip/test-2.gz +0 -0
- package/tests/unit/gzip/test-3 +0 -0
- package/tests/unit/gzip/test-3.gz +0 -0
- package/tests/unit/gzip/test-4 +0 -0
- package/tests/unit/gzip/test-4.gz +0 -0
- package/tests/unit/test-adc.js +0 -307
- package/tests/unit/test-bcd.js +0 -30
- package/tests/unit/test-cmos.js +0 -266
- package/tests/unit/test-disc-drive.js +0 -85
- package/tests/unit/test-disc-hfe.js +0 -347
- package/tests/unit/test-disc.js +0 -232
- package/tests/unit/test-fifo.js +0 -35
- package/tests/unit/test-gamepad-source.js +0 -67
- package/tests/unit/test-gzip.js +0 -22
- package/tests/unit/test-intel-fdc.js +0 -93
- package/tests/unit/test-keyboard.js +0 -410
- package/tests/unit/test-mouse-joystick-source.js +0 -128
- package/tests/unit/test-scheduler.js +0 -190
- package/tests/unit/test-serial.js +0 -154
- package/tests/unit/test-teletext-adaptor.js +0 -359
- package/tests/unit/test-tokenise.js +0 -65
- package/tests/unit/test-url-params.js +0 -398
- package/tests/unit/test-utils.js +0 -276
- package/tests/unit/test-video.js +0 -498
- package/tests/unit/test-zip.js +0 -56
- package/tests/unit/zip/test-mixed.zip +0 -0
- package/tests/unit/zip/test-rom.zip +0 -0
- package/tests/unit/zip/test-ssd.zip +0 -0
- package/tools/fir-generator.js +0 -80
- package/tools/vite-plugin-fir-shader.js +0 -131
- package/vite.config.js +0 -34
package/tests/unit/test-cmos.js
DELETED
|
@@ -1,266 +0,0 @@
|
|
|
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
|
-
});
|
|
@@ -1,85 +0,0 @@
|
|
|
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
|
-
});
|
|
@@ -1,347 +0,0 @@
|
|
|
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
|
-
});
|