web-a2e 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.clangd +5 -0
- package/.mcp.json +12 -0
- package/CLAUDE.md +362 -0
- package/CMakeLists.txt +774 -0
- package/LICENSE +21 -0
- package/README.md +392 -0
- package/build-wasm/generated/roms.cpp +2447 -0
- package/docker-compose.staging.yml +9 -0
- package/docs/basic-rom-disassembly.md +6663 -0
- package/docs/softswitch-comparison.md +273 -0
- package/docs/thunderclock-debug.md +89 -0
- package/examples/cube.bas +72 -0
- package/examples/hello.s +55 -0
- package/examples/scroll.s +140 -0
- package/package.json +18 -0
- package/public/assets/apple-logo-old.png +0 -0
- package/public/assets/apple-logo.png +0 -0
- package/public/assets/drive-closed-light-on.png +0 -0
- package/public/assets/drive-closed.png +0 -0
- package/public/assets/drive-open-light-on.png +0 -0
- package/public/assets/drive-open.png +0 -0
- package/public/audio-worklet.js +82 -0
- package/public/disks/Apple DOS 3.3 January 1983.dsk +0 -0
- package/public/disks/ProDOS 2.4.3.po +0 -0
- package/public/disks/h32mb.2mg +0 -0
- package/public/disks/library.json +26 -0
- package/public/docs/llms/llm-assembler.txt +90 -0
- package/public/docs/llms/llm-basic-program.txt +256 -0
- package/public/docs/llms/llm-disk-drives.txt +72 -0
- package/public/docs/llms/llm-file-explorer.txt +50 -0
- package/public/docs/llms/llm-hard-drives.txt +80 -0
- package/public/docs/llms/llm-main.txt +51 -0
- package/public/docs/llms/llm-slot-configuration.txt +66 -0
- package/public/icons/icon-192.svg +4 -0
- package/public/icons/icon-512.svg +4 -0
- package/public/index.html +661 -0
- package/public/llms.txt +49 -0
- package/public/manifest.json +29 -0
- package/public/shaders/burnin.glsl +22 -0
- package/public/shaders/crt.glsl +706 -0
- package/public/shaders/edge.glsl +109 -0
- package/public/shaders/vertex.glsl +8 -0
- package/public/sw.js +186 -0
- package/roms/341-0027.bin +0 -0
- package/roms/341-0160-A-US-UK.bin +0 -0
- package/roms/341-0160-A.bin +0 -0
- package/roms/342-0273-A-US-UK.bin +0 -0
- package/roms/342-0349-B-C0-FF.bin +0 -0
- package/roms/Apple Mouse Interface Card ROM - 342-0270-C.bin +0 -0
- package/roms/Thunderclock Plus ROM.bin +0 -0
- package/scripts/generate_roms.sh +69 -0
- package/src/bindings/wasm_interface.cpp +1940 -0
- package/src/core/assembler/assembler.cpp +1239 -0
- package/src/core/assembler/assembler.hpp +115 -0
- package/src/core/audio/audio.cpp +160 -0
- package/src/core/audio/audio.hpp +81 -0
- package/src/core/basic/basic_detokenizer.cpp +436 -0
- package/src/core/basic/basic_detokenizer.hpp +41 -0
- package/src/core/basic/basic_tokenizer.cpp +286 -0
- package/src/core/basic/basic_tokenizer.hpp +26 -0
- package/src/core/basic/basic_tokens.hpp +295 -0
- package/src/core/cards/disk2_card.cpp +568 -0
- package/src/core/cards/disk2_card.hpp +316 -0
- package/src/core/cards/expansion_card.hpp +185 -0
- package/src/core/cards/mockingboard/ay8910.cpp +616 -0
- package/src/core/cards/mockingboard/ay8910.hpp +159 -0
- package/src/core/cards/mockingboard/via6522.cpp +530 -0
- package/src/core/cards/mockingboard/via6522.hpp +163 -0
- package/src/core/cards/mockingboard_card.cpp +312 -0
- package/src/core/cards/mockingboard_card.hpp +159 -0
- package/src/core/cards/mouse_card.cpp +654 -0
- package/src/core/cards/mouse_card.hpp +190 -0
- package/src/core/cards/smartport/block_device.cpp +202 -0
- package/src/core/cards/smartport/block_device.hpp +60 -0
- package/src/core/cards/smartport/smartport_card.cpp +603 -0
- package/src/core/cards/smartport/smartport_card.hpp +120 -0
- package/src/core/cards/thunderclock_card.cpp +237 -0
- package/src/core/cards/thunderclock_card.hpp +122 -0
- package/src/core/cpu/cpu6502.cpp +1609 -0
- package/src/core/cpu/cpu6502.hpp +203 -0
- package/src/core/debug/condition_evaluator.cpp +470 -0
- package/src/core/debug/condition_evaluator.hpp +87 -0
- package/src/core/disassembler/disassembler.cpp +552 -0
- package/src/core/disassembler/disassembler.hpp +171 -0
- package/src/core/disk-image/disk_image.hpp +267 -0
- package/src/core/disk-image/dsk_disk_image.cpp +827 -0
- package/src/core/disk-image/dsk_disk_image.hpp +204 -0
- package/src/core/disk-image/gcr_encoding.cpp +147 -0
- package/src/core/disk-image/gcr_encoding.hpp +78 -0
- package/src/core/disk-image/woz_disk_image.cpp +1049 -0
- package/src/core/disk-image/woz_disk_image.hpp +343 -0
- package/src/core/emulator.cpp +2126 -0
- package/src/core/emulator.hpp +434 -0
- package/src/core/filesystem/dos33.cpp +178 -0
- package/src/core/filesystem/dos33.hpp +66 -0
- package/src/core/filesystem/pascal.cpp +262 -0
- package/src/core/filesystem/pascal.hpp +87 -0
- package/src/core/filesystem/prodos.cpp +369 -0
- package/src/core/filesystem/prodos.hpp +119 -0
- package/src/core/input/keyboard.cpp +227 -0
- package/src/core/input/keyboard.hpp +111 -0
- package/src/core/mmu/mmu.cpp +1387 -0
- package/src/core/mmu/mmu.hpp +236 -0
- package/src/core/types.hpp +196 -0
- package/src/core/video/video.cpp +680 -0
- package/src/core/video/video.hpp +156 -0
- package/src/css/assembler-editor.css +1617 -0
- package/src/css/base.css +470 -0
- package/src/css/basic-debugger.css +791 -0
- package/src/css/basic-editor.css +792 -0
- package/src/css/controls.css +783 -0
- package/src/css/cpu-debugger.css +1413 -0
- package/src/css/debug-base.css +160 -0
- package/src/css/debug-windows.css +6455 -0
- package/src/css/disk-drives.css +406 -0
- package/src/css/documentation.css +392 -0
- package/src/css/file-explorer.css +867 -0
- package/src/css/hard-drive.css +180 -0
- package/src/css/layout.css +217 -0
- package/src/css/memory-windows.css +798 -0
- package/src/css/modals.css +510 -0
- package/src/css/monitor.css +425 -0
- package/src/css/release-notes.css +101 -0
- package/src/css/responsive.css +400 -0
- package/src/css/rule-builder.css +340 -0
- package/src/css/save-states.css +201 -0
- package/src/css/settings-windows.css +1231 -0
- package/src/css/window-switcher.css +150 -0
- package/src/js/agent/agent-manager.js +643 -0
- package/src/js/agent/agent-tools.js +293 -0
- package/src/js/agent/agent-version-tools.js +131 -0
- package/src/js/agent/assembler-tools.js +357 -0
- package/src/js/agent/basic-program-tools.js +894 -0
- package/src/js/agent/disk-tools.js +417 -0
- package/src/js/agent/file-explorer-tools.js +269 -0
- package/src/js/agent/index.js +13 -0
- package/src/js/agent/main-tools.js +222 -0
- package/src/js/agent/slot-tools.js +303 -0
- package/src/js/agent/smartport-tools.js +257 -0
- package/src/js/agent/window-tools.js +80 -0
- package/src/js/audio/audio-driver.js +417 -0
- package/src/js/audio/audio-worklet.js +85 -0
- package/src/js/audio/index.js +8 -0
- package/src/js/config/default-layout.js +34 -0
- package/src/js/config/version.js +8 -0
- package/src/js/data/apple2-rom-routines.js +577 -0
- package/src/js/debug/assembler-editor-window.js +2993 -0
- package/src/js/debug/basic-breakpoint-manager.js +529 -0
- package/src/js/debug/basic-program-parser.js +436 -0
- package/src/js/debug/basic-program-window.js +2594 -0
- package/src/js/debug/basic-variable-inspector.js +447 -0
- package/src/js/debug/breakpoint-manager.js +472 -0
- package/src/js/debug/cpu-debugger-window.js +2396 -0
- package/src/js/debug/index.js +22 -0
- package/src/js/debug/label-manager.js +238 -0
- package/src/js/debug/memory-browser-window.js +416 -0
- package/src/js/debug/memory-heat-map-window.js +481 -0
- package/src/js/debug/memory-map-window.js +206 -0
- package/src/js/debug/mockingboard-window.js +882 -0
- package/src/js/debug/mouse-card-window.js +355 -0
- package/src/js/debug/rule-builder-window.js +648 -0
- package/src/js/debug/soft-switch-window.js +458 -0
- package/src/js/debug/stack-viewer-window.js +221 -0
- package/src/js/debug/symbols.js +416 -0
- package/src/js/debug/trace-panel.js +291 -0
- package/src/js/debug/zero-page-watch-window.js +297 -0
- package/src/js/disk-manager/disk-drives-window.js +212 -0
- package/src/js/disk-manager/disk-operations.js +284 -0
- package/src/js/disk-manager/disk-persistence.js +301 -0
- package/src/js/disk-manager/disk-surface-renderer.js +388 -0
- package/src/js/disk-manager/drive-sounds.js +139 -0
- package/src/js/disk-manager/hard-drive-manager.js +481 -0
- package/src/js/disk-manager/hard-drive-persistence.js +187 -0
- package/src/js/disk-manager/hard-drive-window.js +57 -0
- package/src/js/disk-manager/index.js +890 -0
- package/src/js/display/display-settings-window.js +383 -0
- package/src/js/display/index.js +10 -0
- package/src/js/display/screen-window.js +342 -0
- package/src/js/display/webgl-renderer.js +705 -0
- package/src/js/file-explorer/disassembler.js +574 -0
- package/src/js/file-explorer/dos33.js +266 -0
- package/src/js/file-explorer/file-viewer.js +359 -0
- package/src/js/file-explorer/index.js +1261 -0
- package/src/js/file-explorer/prodos.js +549 -0
- package/src/js/file-explorer/utils.js +67 -0
- package/src/js/help/documentation-window.js +1096 -0
- package/src/js/help/index.js +10 -0
- package/src/js/help/release-notes-window.js +85 -0
- package/src/js/help/release-notes.js +612 -0
- package/src/js/input/gamepad-handler.js +176 -0
- package/src/js/input/index.js +12 -0
- package/src/js/input/input-handler.js +396 -0
- package/src/js/input/joystick-window.js +404 -0
- package/src/js/input/mouse-handler.js +99 -0
- package/src/js/input/text-selection.js +462 -0
- package/src/js/main.js +653 -0
- package/src/js/state/index.js +15 -0
- package/src/js/state/save-states-window.js +393 -0
- package/src/js/state/state-manager.js +409 -0
- package/src/js/state/state-persistence.js +218 -0
- package/src/js/ui/confirm.js +43 -0
- package/src/js/ui/disk-drive-positioner.js +347 -0
- package/src/js/ui/reminder-controller.js +129 -0
- package/src/js/ui/slot-configuration-window.js +560 -0
- package/src/js/ui/theme-manager.js +61 -0
- package/src/js/ui/toast.js +44 -0
- package/src/js/ui/ui-controller.js +897 -0
- package/src/js/ui/window-switcher.js +275 -0
- package/src/js/utils/basic-autocomplete.js +832 -0
- package/src/js/utils/basic-highlighting.js +473 -0
- package/src/js/utils/basic-tokenizer.js +153 -0
- package/src/js/utils/basic-tokens.js +117 -0
- package/src/js/utils/constants.js +28 -0
- package/src/js/utils/indexeddb-helper.js +225 -0
- package/src/js/utils/merlin-editor-support.js +905 -0
- package/src/js/utils/merlin-highlighting.js +551 -0
- package/src/js/utils/storage.js +125 -0
- package/src/js/utils/string-utils.js +19 -0
- package/src/js/utils/wasm-memory.js +54 -0
- package/src/js/windows/base-window.js +690 -0
- package/src/js/windows/index.js +9 -0
- package/src/js/windows/window-manager.js +375 -0
- package/tests/catch2/catch.hpp +17976 -0
- package/tests/common/basic_program_builder.cpp +119 -0
- package/tests/common/basic_program_builder.hpp +209 -0
- package/tests/common/disk_image_builder.cpp +444 -0
- package/tests/common/disk_image_builder.hpp +141 -0
- package/tests/common/test_helpers.hpp +118 -0
- package/tests/gcr/gcr-test.cpp +142 -0
- package/tests/integration/check-rom.js +70 -0
- package/tests/integration/compare-boot.js +239 -0
- package/tests/integration/crash-trace.js +102 -0
- package/tests/integration/disk-boot-test.js +264 -0
- package/tests/integration/memory-crash.js +108 -0
- package/tests/integration/nibble-read-test.js +249 -0
- package/tests/integration/phase-test.js +159 -0
- package/tests/integration/test_emulator.cpp +291 -0
- package/tests/integration/test_emulator_basic.cpp +91 -0
- package/tests/integration/test_emulator_debug.cpp +344 -0
- package/tests/integration/test_emulator_disk.cpp +153 -0
- package/tests/integration/test_emulator_state.cpp +163 -0
- package/tests/klaus/6502_functional_test.bin +0 -0
- package/tests/klaus/65C02_extended_opcodes_test.bin +0 -0
- package/tests/klaus/klaus_6502_test.cpp +184 -0
- package/tests/klaus/klaus_65c02_test.cpp +197 -0
- package/tests/thunderclock/thunderclock_mmu_test.cpp +304 -0
- package/tests/thunderclock/thunderclock_test.cpp +550 -0
- package/tests/unit/test_assembler.cpp +521 -0
- package/tests/unit/test_audio.cpp +196 -0
- package/tests/unit/test_ay8910.cpp +311 -0
- package/tests/unit/test_basic_detokenizer.cpp +265 -0
- package/tests/unit/test_basic_tokenizer.cpp +382 -0
- package/tests/unit/test_block_device.cpp +259 -0
- package/tests/unit/test_condition_evaluator.cpp +219 -0
- package/tests/unit/test_cpu6502.cpp +1301 -0
- package/tests/unit/test_cpu_addressing.cpp +361 -0
- package/tests/unit/test_cpu_cycle_counts.cpp +409 -0
- package/tests/unit/test_cpu_decimal.cpp +166 -0
- package/tests/unit/test_cpu_interrupts.cpp +285 -0
- package/tests/unit/test_disassembler.cpp +323 -0
- package/tests/unit/test_disk2_card.cpp +330 -0
- package/tests/unit/test_dos33.cpp +273 -0
- package/tests/unit/test_dsk_disk_image.cpp +315 -0
- package/tests/unit/test_expansion_card.cpp +178 -0
- package/tests/unit/test_gcr_encoding.cpp +232 -0
- package/tests/unit/test_keyboard.cpp +262 -0
- package/tests/unit/test_mmu.cpp +555 -0
- package/tests/unit/test_mmu_slots.cpp +323 -0
- package/tests/unit/test_mockingboard.cpp +352 -0
- package/tests/unit/test_mouse_card.cpp +386 -0
- package/tests/unit/test_pascal.cpp +248 -0
- package/tests/unit/test_prodos.cpp +259 -0
- package/tests/unit/test_smartport_card.cpp +321 -0
- package/tests/unit/test_thunderclock.cpp +354 -0
- package/tests/unit/test_via6522.cpp +323 -0
- package/tests/unit/test_video.cpp +319 -0
- package/tests/unit/test_woz_disk_image.cpp +257 -0
- package/vite.config.js +96 -0
- package/wiki/AI-Agent.md +372 -0
- package/wiki/Architecture-Overview.md +303 -0
- package/wiki/Audio-System.md +449 -0
- package/wiki/CPU-Emulation.md +477 -0
- package/wiki/Debugger.md +516 -0
- package/wiki/Disk-Drives.md +161 -0
- package/wiki/Disk-System-Internals.md +547 -0
- package/wiki/Display-Settings.md +88 -0
- package/wiki/Expansion-Slots.md +187 -0
- package/wiki/File-Explorer.md +259 -0
- package/wiki/Getting-Started.md +156 -0
- package/wiki/Home.md +69 -0
- package/wiki/Input-Devices.md +183 -0
- package/wiki/Keyboard-Shortcuts.md +158 -0
- package/wiki/Memory-System.md +364 -0
- package/wiki/Save-States.md +172 -0
- package/wiki/Video-Rendering.md +658 -0
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
import { readFileSync } from 'fs';
|
|
2
|
+
import { fileURLToPath } from 'url';
|
|
3
|
+
import { dirname, join } from 'path';
|
|
4
|
+
import { createContext, runInContext } from 'vm';
|
|
5
|
+
|
|
6
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
7
|
+
const __dirname = dirname(__filename);
|
|
8
|
+
|
|
9
|
+
async function loadWasmModule() {
|
|
10
|
+
const wasmJsPath = join(__dirname, '..', 'public', 'a2e.js');
|
|
11
|
+
const wasmDir = dirname(wasmJsPath);
|
|
12
|
+
const code = readFileSync(wasmJsPath, 'utf8');
|
|
13
|
+
const context = {
|
|
14
|
+
module: { exports: {} },
|
|
15
|
+
exports: {},
|
|
16
|
+
globalThis: global,
|
|
17
|
+
console: console,
|
|
18
|
+
require: (await import('module')).createRequire(import.meta.url),
|
|
19
|
+
__dirname: wasmDir,
|
|
20
|
+
__filename: wasmJsPath,
|
|
21
|
+
process: process,
|
|
22
|
+
WebAssembly: WebAssembly,
|
|
23
|
+
URL: URL,
|
|
24
|
+
TextDecoder: TextDecoder,
|
|
25
|
+
setTimeout: setTimeout,
|
|
26
|
+
clearTimeout: clearTimeout,
|
|
27
|
+
performance: { now: () => Date.now() }
|
|
28
|
+
};
|
|
29
|
+
createContext(context);
|
|
30
|
+
runInContext(code, context);
|
|
31
|
+
return context.module.exports;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
async function runTest() {
|
|
35
|
+
const createA2EModule = await loadWasmModule();
|
|
36
|
+
const Module = await createA2EModule();
|
|
37
|
+
Module._init();
|
|
38
|
+
|
|
39
|
+
const diskPath = join(__dirname, '..', 'public', 'Apple DOS 3.3 August 1980.dsk');
|
|
40
|
+
const diskData = readFileSync(diskPath);
|
|
41
|
+
const diskPtr = Module._malloc(diskData.length);
|
|
42
|
+
Module.HEAPU8.set(diskData, diskPtr);
|
|
43
|
+
const filename = 'test.dsk';
|
|
44
|
+
const filenamePtr = Module._malloc(filename.length + 1);
|
|
45
|
+
Module.stringToUTF8(filename, filenamePtr, filename.length + 1);
|
|
46
|
+
Module._insertDisk(0, diskPtr, diskData.length, filenamePtr);
|
|
47
|
+
Module._free(filenamePtr);
|
|
48
|
+
|
|
49
|
+
console.log('=== PHASE/HEAD MOVEMENT TEST ===\n');
|
|
50
|
+
|
|
51
|
+
// Manually test phase switching by writing to soft switches
|
|
52
|
+
// Slot 6 soft switches: $C0E0-$C0EF
|
|
53
|
+
// Phase 0: $C0E0 (off), $C0E1 (on)
|
|
54
|
+
// Phase 1: $C0E2 (off), $C0E3 (on)
|
|
55
|
+
// Phase 2: $C0E4 (off), $C0E5 (on)
|
|
56
|
+
// Phase 3: $C0E6 (off), $C0E7 (on)
|
|
57
|
+
// Motor: $C0E8 (off), $C0E9 (on)
|
|
58
|
+
|
|
59
|
+
const getTrack = () => Module._getDiskTrack(0);
|
|
60
|
+
const getQuarterTrack = () => Module._getDiskHeadPosition(0);
|
|
61
|
+
const getPhaseStates = () => Module._getDiskPhase(0);
|
|
62
|
+
|
|
63
|
+
console.log('Initial state:');
|
|
64
|
+
console.log(' Track: ' + getTrack() + ', Quarter-track: ' + getQuarterTrack() + ', Phases: ' + getPhaseStates().toString(2).padStart(4, '0'));
|
|
65
|
+
|
|
66
|
+
// Turn on motor
|
|
67
|
+
Module._readMemory(0xC0E9); // Motor on
|
|
68
|
+
Module._runCycles(100);
|
|
69
|
+
|
|
70
|
+
console.log('\nAfter motor on:');
|
|
71
|
+
console.log(' Motor: ' + Module._getDiskMotorOn(0));
|
|
72
|
+
|
|
73
|
+
// Test stepping from track 0 to track 1
|
|
74
|
+
// Starting at track 0 (quarter-track 0), phase 0 should be on
|
|
75
|
+
// To move to track 1 (quarter-track 4):
|
|
76
|
+
// Phase 0 -> Phase 1 (+2 QT) -> Phase 2 (+2 QT) = quarter-track 4
|
|
77
|
+
|
|
78
|
+
console.log('\n=== Testing step from track 0 to track 1 ===');
|
|
79
|
+
console.log('Expected sequence: Phase 0 -> 1 -> 2 (quarter-tracks 0 -> 2 -> 4)');
|
|
80
|
+
|
|
81
|
+
// Make sure we start at track 0
|
|
82
|
+
Module._readMemory(0xC0E1); // Phase 0 on
|
|
83
|
+
Module._runCycles(10);
|
|
84
|
+
console.log('\nPhase 0 on:');
|
|
85
|
+
console.log(' Quarter-track: ' + getQuarterTrack() + ', Phases: ' + getPhaseStates().toString(2).padStart(4, '0'));
|
|
86
|
+
|
|
87
|
+
// Step to phase 1
|
|
88
|
+
Module._readMemory(0xC0E0); // Phase 0 off
|
|
89
|
+
Module._readMemory(0xC0E3); // Phase 1 on
|
|
90
|
+
Module._runCycles(10);
|
|
91
|
+
console.log('\nPhase 1 on (phase 0 off):');
|
|
92
|
+
console.log(' Quarter-track: ' + getQuarterTrack() + ', Track: ' + getTrack() + ', Phases: ' + getPhaseStates().toString(2).padStart(4, '0'));
|
|
93
|
+
|
|
94
|
+
// Step to phase 2
|
|
95
|
+
Module._readMemory(0xC0E2); // Phase 1 off
|
|
96
|
+
Module._readMemory(0xC0E5); // Phase 2 on
|
|
97
|
+
Module._runCycles(10);
|
|
98
|
+
console.log('\nPhase 2 on (phase 1 off):');
|
|
99
|
+
console.log(' Quarter-track: ' + getQuarterTrack() + ', Track: ' + getTrack() + ', Phases: ' + getPhaseStates().toString(2).padStart(4, '0'));
|
|
100
|
+
|
|
101
|
+
// Should now be at track 1 (quarter-track 4)
|
|
102
|
+
console.log('\n=== Expected: Quarter-track 4, Track 1 ===');
|
|
103
|
+
|
|
104
|
+
// Test stepping back to track 0
|
|
105
|
+
console.log('\n=== Testing step from track 1 back to track 0 ===');
|
|
106
|
+
console.log('Expected sequence: Phase 2 -> 1 -> 0 (quarter-tracks 4 -> 2 -> 0)');
|
|
107
|
+
|
|
108
|
+
// Step to phase 1
|
|
109
|
+
Module._readMemory(0xC0E4); // Phase 2 off
|
|
110
|
+
Module._readMemory(0xC0E3); // Phase 1 on
|
|
111
|
+
Module._runCycles(10);
|
|
112
|
+
console.log('\nPhase 1 on (phase 2 off):');
|
|
113
|
+
console.log(' Quarter-track: ' + getQuarterTrack() + ', Track: ' + getTrack() + ', Phases: ' + getPhaseStates().toString(2).padStart(4, '0'));
|
|
114
|
+
|
|
115
|
+
// Step to phase 0
|
|
116
|
+
Module._readMemory(0xC0E2); // Phase 1 off
|
|
117
|
+
Module._readMemory(0xC0E1); // Phase 0 on
|
|
118
|
+
Module._runCycles(10);
|
|
119
|
+
console.log('\nPhase 0 on (phase 1 off):');
|
|
120
|
+
console.log(' Quarter-track: ' + getQuarterTrack() + ', Track: ' + getTrack() + ', Phases: ' + getPhaseStates().toString(2).padStart(4, '0'));
|
|
121
|
+
|
|
122
|
+
console.log('\n=== Expected: Quarter-track 0, Track 0 ===');
|
|
123
|
+
|
|
124
|
+
// Now test multiple track stepping
|
|
125
|
+
console.log('\n=== Testing multiple track step (track 0 -> track 5) ===');
|
|
126
|
+
|
|
127
|
+
// Reset to track 0
|
|
128
|
+
Module._readMemory(0xC0E1); // Phase 0 on
|
|
129
|
+
Module._runCycles(10);
|
|
130
|
+
|
|
131
|
+
for (let targetTrack = 1; targetTrack <= 5; targetTrack++) {
|
|
132
|
+
// Step through phases: 0 -> 1 -> 2 -> 3 -> 0 (repeats)
|
|
133
|
+
const phases = [0xC0E1, 0xC0E3, 0xC0E5, 0xC0E7]; // Phase on addresses
|
|
134
|
+
const phaseOff = [0xC0E0, 0xC0E2, 0xC0E4, 0xC0E6]; // Phase off addresses
|
|
135
|
+
|
|
136
|
+
// Calculate current phase from quarter-track
|
|
137
|
+
const qt = getQuarterTrack();
|
|
138
|
+
const currentPhase = (qt / 2) % 4;
|
|
139
|
+
const nextPhase = (currentPhase + 1) % 4;
|
|
140
|
+
|
|
141
|
+
// Turn off current phase
|
|
142
|
+
Module._readMemory(phaseOff[currentPhase]);
|
|
143
|
+
// Turn on next phase
|
|
144
|
+
Module._readMemory(phases[nextPhase]);
|
|
145
|
+
Module._runCycles(10);
|
|
146
|
+
|
|
147
|
+
// Step again to complete the track
|
|
148
|
+
const phase2 = (nextPhase + 1) % 4;
|
|
149
|
+
Module._readMemory(phaseOff[nextPhase]);
|
|
150
|
+
Module._readMemory(phases[phase2]);
|
|
151
|
+
Module._runCycles(10);
|
|
152
|
+
|
|
153
|
+
console.log('After stepping to track ' + targetTrack + ': Quarter-track=' + getQuarterTrack() + ', Track=' + getTrack());
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
Module._free(diskPtr);
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
runTest().catch(err => { console.error(err); process.exit(1); });
|
|
@@ -0,0 +1,291 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* test_emulator.cpp - Integration tests for the Emulator class
|
|
3
|
+
*
|
|
4
|
+
* Tests the full emulator coordinator including initialization, reset,
|
|
5
|
+
* execution, memory access, video, beam position, soft switches,
|
|
6
|
+
* slot management, screen text, disassembly, and speed control.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
#define CATCH_CONFIG_MAIN
|
|
10
|
+
#include "catch.hpp"
|
|
11
|
+
|
|
12
|
+
#include "emulator.hpp"
|
|
13
|
+
|
|
14
|
+
using namespace a2e;
|
|
15
|
+
|
|
16
|
+
// ---------------------------------------------------------------------------
|
|
17
|
+
// Initialization
|
|
18
|
+
// ---------------------------------------------------------------------------
|
|
19
|
+
|
|
20
|
+
TEST_CASE("Emulator init does not crash", "[emulator][init]") {
|
|
21
|
+
Emulator emu;
|
|
22
|
+
REQUIRE_NOTHROW(emu.init());
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
TEST_CASE("Emulator PC points to reset vector destination after init", "[emulator][init]") {
|
|
26
|
+
Emulator emu;
|
|
27
|
+
emu.init();
|
|
28
|
+
|
|
29
|
+
// After reset, the 65C02 reads the reset vector at $FFFC/$FFFD and sets
|
|
30
|
+
// PC to that address. The Apple IIe ROM reset vector points into the
|
|
31
|
+
// $FA00-$FFFF range (monitor / reset handler).
|
|
32
|
+
uint16_t pc = emu.getPC();
|
|
33
|
+
REQUIRE(pc >= 0xC000);
|
|
34
|
+
REQUIRE(pc <= 0xFFFF);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
// ---------------------------------------------------------------------------
|
|
38
|
+
// Reset
|
|
39
|
+
// ---------------------------------------------------------------------------
|
|
40
|
+
|
|
41
|
+
TEST_CASE("Emulator reset returns PC to reset vector", "[emulator][reset]") {
|
|
42
|
+
Emulator emu;
|
|
43
|
+
emu.init();
|
|
44
|
+
|
|
45
|
+
uint16_t pcAfterInit = emu.getPC();
|
|
46
|
+
|
|
47
|
+
// Run some cycles to move PC away from the reset address
|
|
48
|
+
emu.runCycles(1000);
|
|
49
|
+
REQUIRE(emu.getPC() != pcAfterInit);
|
|
50
|
+
|
|
51
|
+
// Reset should return PC to the same reset vector destination
|
|
52
|
+
emu.reset();
|
|
53
|
+
REQUIRE(emu.getPC() == pcAfterInit);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
TEST_CASE("Emulator warmReset preserves memory but resets PC", "[emulator][reset]") {
|
|
57
|
+
Emulator emu;
|
|
58
|
+
emu.init();
|
|
59
|
+
|
|
60
|
+
uint16_t pcAfterInit = emu.getPC();
|
|
61
|
+
|
|
62
|
+
// Write a known value into low RAM
|
|
63
|
+
emu.writeMemory(0x0300, 0xAB);
|
|
64
|
+
REQUIRE(emu.readMemory(0x0300) == 0xAB);
|
|
65
|
+
|
|
66
|
+
// Run some cycles to move PC
|
|
67
|
+
emu.runCycles(1000);
|
|
68
|
+
|
|
69
|
+
// Warm reset: memory preserved, PC returns to reset vector
|
|
70
|
+
emu.warmReset();
|
|
71
|
+
REQUIRE(emu.getPC() == pcAfterInit);
|
|
72
|
+
REQUIRE(emu.readMemory(0x0300) == 0xAB);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
// ---------------------------------------------------------------------------
|
|
76
|
+
// Execution
|
|
77
|
+
// ---------------------------------------------------------------------------
|
|
78
|
+
|
|
79
|
+
TEST_CASE("Emulator runCycles advances cycles and PC", "[emulator][execution]") {
|
|
80
|
+
Emulator emu;
|
|
81
|
+
emu.init();
|
|
82
|
+
|
|
83
|
+
uint16_t pcBefore = emu.getPC();
|
|
84
|
+
uint64_t cyclesBefore = emu.getTotalCycles();
|
|
85
|
+
|
|
86
|
+
emu.runCycles(100);
|
|
87
|
+
|
|
88
|
+
REQUIRE(emu.getTotalCycles() > cyclesBefore);
|
|
89
|
+
REQUIRE(emu.getPC() != pcBefore);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
TEST_CASE("Emulator stepInstruction executes one instruction", "[emulator][execution]") {
|
|
93
|
+
Emulator emu;
|
|
94
|
+
emu.init();
|
|
95
|
+
|
|
96
|
+
uint64_t cyclesBefore = emu.getTotalCycles();
|
|
97
|
+
uint16_t pcBefore = emu.getPC();
|
|
98
|
+
|
|
99
|
+
emu.stepInstruction();
|
|
100
|
+
|
|
101
|
+
// At least 2 cycles for the shortest 65C02 instruction, PC should have moved
|
|
102
|
+
REQUIRE(emu.getTotalCycles() > cyclesBefore);
|
|
103
|
+
REQUIRE(emu.getTotalCycles() <= cyclesBefore + 7); // max 7 cycles for any instruction
|
|
104
|
+
REQUIRE(emu.getPC() != pcBefore);
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
// ---------------------------------------------------------------------------
|
|
108
|
+
// Pause
|
|
109
|
+
// ---------------------------------------------------------------------------
|
|
110
|
+
|
|
111
|
+
TEST_CASE("Emulator setPaused and isPaused", "[emulator][pause]") {
|
|
112
|
+
Emulator emu;
|
|
113
|
+
emu.init();
|
|
114
|
+
|
|
115
|
+
REQUIRE_FALSE(emu.isPaused());
|
|
116
|
+
|
|
117
|
+
emu.setPaused(true);
|
|
118
|
+
REQUIRE(emu.isPaused());
|
|
119
|
+
|
|
120
|
+
// runCycles should not advance when paused
|
|
121
|
+
uint64_t cyclesBefore = emu.getTotalCycles();
|
|
122
|
+
emu.runCycles(1000);
|
|
123
|
+
REQUIRE(emu.getTotalCycles() == cyclesBefore);
|
|
124
|
+
|
|
125
|
+
emu.setPaused(false);
|
|
126
|
+
REQUIRE_FALSE(emu.isPaused());
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
// ---------------------------------------------------------------------------
|
|
130
|
+
// Memory access
|
|
131
|
+
// ---------------------------------------------------------------------------
|
|
132
|
+
|
|
133
|
+
TEST_CASE("Emulator writeMemory and readMemory round-trip", "[emulator][memory]") {
|
|
134
|
+
Emulator emu;
|
|
135
|
+
emu.init();
|
|
136
|
+
|
|
137
|
+
emu.writeMemory(0x0400, 0x42);
|
|
138
|
+
REQUIRE(emu.readMemory(0x0400) == 0x42);
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
TEST_CASE("Emulator peekMemory reads same as readMemory for normal RAM", "[emulator][memory]") {
|
|
142
|
+
Emulator emu;
|
|
143
|
+
emu.init();
|
|
144
|
+
|
|
145
|
+
emu.writeMemory(0x0400, 0x55);
|
|
146
|
+
REQUIRE(emu.peekMemory(0x0400) == emu.readMemory(0x0400));
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
// ---------------------------------------------------------------------------
|
|
150
|
+
// Video / Framebuffer
|
|
151
|
+
// ---------------------------------------------------------------------------
|
|
152
|
+
|
|
153
|
+
TEST_CASE("Emulator getFramebuffer returns non-null after init", "[emulator][video]") {
|
|
154
|
+
Emulator emu;
|
|
155
|
+
emu.init();
|
|
156
|
+
|
|
157
|
+
const uint8_t* fb = emu.getFramebuffer();
|
|
158
|
+
REQUIRE(fb != nullptr);
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
TEST_CASE("Emulator getFramebufferSize equals expected RGBA buffer size", "[emulator][video]") {
|
|
162
|
+
Emulator emu;
|
|
163
|
+
emu.init();
|
|
164
|
+
|
|
165
|
+
// 560 * 384 * 4 (RGBA) = 860160
|
|
166
|
+
REQUIRE(emu.getFramebufferSize() == 860160);
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
// ---------------------------------------------------------------------------
|
|
170
|
+
// Beam position
|
|
171
|
+
// ---------------------------------------------------------------------------
|
|
172
|
+
|
|
173
|
+
TEST_CASE("Emulator beam position returns valid scanline and hPos", "[emulator][beam]") {
|
|
174
|
+
Emulator emu;
|
|
175
|
+
emu.init();
|
|
176
|
+
|
|
177
|
+
// Run a few cycles so beam position is somewhere meaningful
|
|
178
|
+
emu.runCycles(200);
|
|
179
|
+
|
|
180
|
+
int scanline = emu.getBeamScanline();
|
|
181
|
+
int hPos = emu.getBeamHPos();
|
|
182
|
+
|
|
183
|
+
REQUIRE(scanline >= 0);
|
|
184
|
+
REQUIRE(scanline < 262); // 262 scanlines per NTSC frame
|
|
185
|
+
REQUIRE(hPos >= 0);
|
|
186
|
+
REQUIRE(hPos < 65); // 65 cycles per scanline
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
// ---------------------------------------------------------------------------
|
|
190
|
+
// Soft switches
|
|
191
|
+
// ---------------------------------------------------------------------------
|
|
192
|
+
|
|
193
|
+
TEST_CASE("Emulator getSoftSwitchState returns packed state", "[emulator][softswitch]") {
|
|
194
|
+
Emulator emu;
|
|
195
|
+
emu.init();
|
|
196
|
+
|
|
197
|
+
// getSoftSwitchState returns a 64-bit packed value; just verify it is callable
|
|
198
|
+
// and produces a reasonable value (all zeros except TEXT mode which is set by default)
|
|
199
|
+
uint64_t state = emu.getSoftSwitchState();
|
|
200
|
+
// TEXT mode bit (bit 0) should be set on a fresh init
|
|
201
|
+
REQUIRE((state & 0x01) == 0x01);
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
// ---------------------------------------------------------------------------
|
|
205
|
+
// Slot management
|
|
206
|
+
// ---------------------------------------------------------------------------
|
|
207
|
+
|
|
208
|
+
TEST_CASE("Emulator slot management: isSlotEmpty and getSlotCardName", "[emulator][slots]") {
|
|
209
|
+
Emulator emu;
|
|
210
|
+
emu.init();
|
|
211
|
+
|
|
212
|
+
SECTION("Slot 3 is never empty (built-in 80-column)") {
|
|
213
|
+
REQUIRE_FALSE(emu.isSlotEmpty(3));
|
|
214
|
+
REQUIRE(std::string(emu.getSlotCardName(3)) == "80col");
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
SECTION("Slot 6 has Disk II by default") {
|
|
218
|
+
REQUIRE_FALSE(emu.isSlotEmpty(6));
|
|
219
|
+
REQUIRE(std::string(emu.getSlotCardName(6)) == "disk2");
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
SECTION("Slot 4 has Mockingboard by default") {
|
|
223
|
+
REQUIRE_FALSE(emu.isSlotEmpty(4));
|
|
224
|
+
REQUIRE(std::string(emu.getSlotCardName(4)) == "mockingboard");
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
SECTION("Slot 1 is empty by default") {
|
|
228
|
+
REQUIRE(emu.isSlotEmpty(1));
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
// ---------------------------------------------------------------------------
|
|
233
|
+
// Screen text
|
|
234
|
+
// ---------------------------------------------------------------------------
|
|
235
|
+
|
|
236
|
+
TEST_CASE("Emulator readScreenText returns a string", "[emulator][screen]") {
|
|
237
|
+
Emulator emu;
|
|
238
|
+
emu.init();
|
|
239
|
+
|
|
240
|
+
// Read full screen (24 rows x 40 cols)
|
|
241
|
+
const char* text = emu.readScreenText(0, 0, 23, 39);
|
|
242
|
+
REQUIRE(text != nullptr);
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
// ---------------------------------------------------------------------------
|
|
246
|
+
// Disassembly
|
|
247
|
+
// ---------------------------------------------------------------------------
|
|
248
|
+
|
|
249
|
+
TEST_CASE("Emulator disassembleAt returns non-empty string", "[emulator][disasm]") {
|
|
250
|
+
Emulator emu;
|
|
251
|
+
emu.init();
|
|
252
|
+
|
|
253
|
+
// Disassemble at current PC (should be in ROM after reset)
|
|
254
|
+
const char* disasm = emu.disassembleAt(emu.getPC());
|
|
255
|
+
REQUIRE(disasm != nullptr);
|
|
256
|
+
REQUIRE(std::string(disasm).length() > 0);
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
// ---------------------------------------------------------------------------
|
|
260
|
+
// Speed control
|
|
261
|
+
// ---------------------------------------------------------------------------
|
|
262
|
+
|
|
263
|
+
TEST_CASE("Emulator speed multiplier set and get", "[emulator][speed]") {
|
|
264
|
+
Emulator emu;
|
|
265
|
+
emu.init();
|
|
266
|
+
|
|
267
|
+
REQUIRE(emu.getSpeedMultiplier() == 1);
|
|
268
|
+
|
|
269
|
+
emu.setSpeedMultiplier(2);
|
|
270
|
+
REQUIRE(emu.getSpeedMultiplier() == 2);
|
|
271
|
+
|
|
272
|
+
emu.setSpeedMultiplier(8);
|
|
273
|
+
REQUIRE(emu.getSpeedMultiplier() == 8);
|
|
274
|
+
|
|
275
|
+
// Values are clamped to 1-8
|
|
276
|
+
emu.setSpeedMultiplier(0);
|
|
277
|
+
REQUIRE(emu.getSpeedMultiplier() == 1);
|
|
278
|
+
|
|
279
|
+
emu.setSpeedMultiplier(100);
|
|
280
|
+
REQUIRE(emu.getSpeedMultiplier() == 8);
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
// ---------------------------------------------------------------------------
|
|
284
|
+
// screenCodeToAscii
|
|
285
|
+
// ---------------------------------------------------------------------------
|
|
286
|
+
|
|
287
|
+
TEST_CASE("Emulator screenCodeToAscii converts known codes", "[emulator][screen]") {
|
|
288
|
+
// Normal ASCII 'A' = 0xC1 in Apple II screen memory
|
|
289
|
+
int result = Emulator::screenCodeToAscii(0xC1);
|
|
290
|
+
REQUIRE(result == 'A');
|
|
291
|
+
}
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* test_emulator_basic.cpp - Integration tests for Emulator BASIC debugging
|
|
3
|
+
*
|
|
4
|
+
* Tests BASIC program tracking, BASIC breakpoints, BASIC error state,
|
|
5
|
+
* and related BASIC debugging API through the Emulator facade.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
#define CATCH_CONFIG_MAIN
|
|
9
|
+
#include "catch.hpp"
|
|
10
|
+
|
|
11
|
+
#include "emulator.hpp"
|
|
12
|
+
|
|
13
|
+
using namespace a2e;
|
|
14
|
+
|
|
15
|
+
// ---------------------------------------------------------------------------
|
|
16
|
+
// BASIC program running state
|
|
17
|
+
// ---------------------------------------------------------------------------
|
|
18
|
+
|
|
19
|
+
TEST_CASE("Emulator isBasicProgramRunning is false after init", "[emulator][basic]") {
|
|
20
|
+
Emulator emu;
|
|
21
|
+
emu.init();
|
|
22
|
+
|
|
23
|
+
REQUIRE_FALSE(emu.isBasicProgramRunning());
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
// ---------------------------------------------------------------------------
|
|
27
|
+
// BASIC breakpoints
|
|
28
|
+
// ---------------------------------------------------------------------------
|
|
29
|
+
|
|
30
|
+
TEST_CASE("Emulator addBasicBreakpoint makes hasBasicBreakpoints true", "[emulator][basic][breakpoint]") {
|
|
31
|
+
Emulator emu;
|
|
32
|
+
emu.init();
|
|
33
|
+
|
|
34
|
+
REQUIRE_FALSE(emu.hasBasicBreakpoints());
|
|
35
|
+
|
|
36
|
+
emu.addBasicBreakpoint(10, -1); // Line 10, whole line
|
|
37
|
+
REQUIRE(emu.hasBasicBreakpoints());
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
TEST_CASE("Emulator clearBasicBreakpoints removes all BASIC breakpoints", "[emulator][basic][breakpoint]") {
|
|
41
|
+
Emulator emu;
|
|
42
|
+
emu.init();
|
|
43
|
+
|
|
44
|
+
emu.addBasicBreakpoint(10, -1);
|
|
45
|
+
emu.addBasicBreakpoint(20, 0);
|
|
46
|
+
REQUIRE(emu.hasBasicBreakpoints());
|
|
47
|
+
|
|
48
|
+
emu.clearBasicBreakpoints();
|
|
49
|
+
REQUIRE_FALSE(emu.hasBasicBreakpoints());
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
TEST_CASE("Emulator removeBasicBreakpoint removes specific breakpoint", "[emulator][basic][breakpoint]") {
|
|
53
|
+
Emulator emu;
|
|
54
|
+
emu.init();
|
|
55
|
+
|
|
56
|
+
emu.addBasicBreakpoint(10, -1);
|
|
57
|
+
emu.removeBasicBreakpoint(10, -1);
|
|
58
|
+
|
|
59
|
+
REQUIRE_FALSE(emu.hasBasicBreakpoints());
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
// ---------------------------------------------------------------------------
|
|
63
|
+
// BASIC breakpoint hit state
|
|
64
|
+
// ---------------------------------------------------------------------------
|
|
65
|
+
|
|
66
|
+
TEST_CASE("Emulator isBasicBreakpointHit is initially false", "[emulator][basic][breakpoint]") {
|
|
67
|
+
Emulator emu;
|
|
68
|
+
emu.init();
|
|
69
|
+
|
|
70
|
+
REQUIRE_FALSE(emu.isBasicBreakpointHit());
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
TEST_CASE("Emulator clearBasicBreakpointHit clears hit state", "[emulator][basic][breakpoint]") {
|
|
74
|
+
Emulator emu;
|
|
75
|
+
emu.init();
|
|
76
|
+
|
|
77
|
+
// Hit state should already be false; clearing it should not crash
|
|
78
|
+
emu.clearBasicBreakpointHit();
|
|
79
|
+
REQUIRE_FALSE(emu.isBasicBreakpointHit());
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
// ---------------------------------------------------------------------------
|
|
83
|
+
// BASIC error state
|
|
84
|
+
// ---------------------------------------------------------------------------
|
|
85
|
+
|
|
86
|
+
TEST_CASE("Emulator isBasicErrorHit is initially false", "[emulator][basic][error]") {
|
|
87
|
+
Emulator emu;
|
|
88
|
+
emu.init();
|
|
89
|
+
|
|
90
|
+
REQUIRE_FALSE(emu.isBasicErrorHit());
|
|
91
|
+
}
|