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.
Files changed (295) hide show
  1. package/.clangd +5 -0
  2. package/.mcp.json +12 -0
  3. package/CLAUDE.md +362 -0
  4. package/CMakeLists.txt +774 -0
  5. package/LICENSE +21 -0
  6. package/README.md +392 -0
  7. package/build-wasm/generated/roms.cpp +2447 -0
  8. package/docker-compose.staging.yml +9 -0
  9. package/docs/basic-rom-disassembly.md +6663 -0
  10. package/docs/softswitch-comparison.md +273 -0
  11. package/docs/thunderclock-debug.md +89 -0
  12. package/examples/cube.bas +72 -0
  13. package/examples/hello.s +55 -0
  14. package/examples/scroll.s +140 -0
  15. package/package.json +18 -0
  16. package/public/assets/apple-logo-old.png +0 -0
  17. package/public/assets/apple-logo.png +0 -0
  18. package/public/assets/drive-closed-light-on.png +0 -0
  19. package/public/assets/drive-closed.png +0 -0
  20. package/public/assets/drive-open-light-on.png +0 -0
  21. package/public/assets/drive-open.png +0 -0
  22. package/public/audio-worklet.js +82 -0
  23. package/public/disks/Apple DOS 3.3 January 1983.dsk +0 -0
  24. package/public/disks/ProDOS 2.4.3.po +0 -0
  25. package/public/disks/h32mb.2mg +0 -0
  26. package/public/disks/library.json +26 -0
  27. package/public/docs/llms/llm-assembler.txt +90 -0
  28. package/public/docs/llms/llm-basic-program.txt +256 -0
  29. package/public/docs/llms/llm-disk-drives.txt +72 -0
  30. package/public/docs/llms/llm-file-explorer.txt +50 -0
  31. package/public/docs/llms/llm-hard-drives.txt +80 -0
  32. package/public/docs/llms/llm-main.txt +51 -0
  33. package/public/docs/llms/llm-slot-configuration.txt +66 -0
  34. package/public/icons/icon-192.svg +4 -0
  35. package/public/icons/icon-512.svg +4 -0
  36. package/public/index.html +661 -0
  37. package/public/llms.txt +49 -0
  38. package/public/manifest.json +29 -0
  39. package/public/shaders/burnin.glsl +22 -0
  40. package/public/shaders/crt.glsl +706 -0
  41. package/public/shaders/edge.glsl +109 -0
  42. package/public/shaders/vertex.glsl +8 -0
  43. package/public/sw.js +186 -0
  44. package/roms/341-0027.bin +0 -0
  45. package/roms/341-0160-A-US-UK.bin +0 -0
  46. package/roms/341-0160-A.bin +0 -0
  47. package/roms/342-0273-A-US-UK.bin +0 -0
  48. package/roms/342-0349-B-C0-FF.bin +0 -0
  49. package/roms/Apple Mouse Interface Card ROM - 342-0270-C.bin +0 -0
  50. package/roms/Thunderclock Plus ROM.bin +0 -0
  51. package/scripts/generate_roms.sh +69 -0
  52. package/src/bindings/wasm_interface.cpp +1940 -0
  53. package/src/core/assembler/assembler.cpp +1239 -0
  54. package/src/core/assembler/assembler.hpp +115 -0
  55. package/src/core/audio/audio.cpp +160 -0
  56. package/src/core/audio/audio.hpp +81 -0
  57. package/src/core/basic/basic_detokenizer.cpp +436 -0
  58. package/src/core/basic/basic_detokenizer.hpp +41 -0
  59. package/src/core/basic/basic_tokenizer.cpp +286 -0
  60. package/src/core/basic/basic_tokenizer.hpp +26 -0
  61. package/src/core/basic/basic_tokens.hpp +295 -0
  62. package/src/core/cards/disk2_card.cpp +568 -0
  63. package/src/core/cards/disk2_card.hpp +316 -0
  64. package/src/core/cards/expansion_card.hpp +185 -0
  65. package/src/core/cards/mockingboard/ay8910.cpp +616 -0
  66. package/src/core/cards/mockingboard/ay8910.hpp +159 -0
  67. package/src/core/cards/mockingboard/via6522.cpp +530 -0
  68. package/src/core/cards/mockingboard/via6522.hpp +163 -0
  69. package/src/core/cards/mockingboard_card.cpp +312 -0
  70. package/src/core/cards/mockingboard_card.hpp +159 -0
  71. package/src/core/cards/mouse_card.cpp +654 -0
  72. package/src/core/cards/mouse_card.hpp +190 -0
  73. package/src/core/cards/smartport/block_device.cpp +202 -0
  74. package/src/core/cards/smartport/block_device.hpp +60 -0
  75. package/src/core/cards/smartport/smartport_card.cpp +603 -0
  76. package/src/core/cards/smartport/smartport_card.hpp +120 -0
  77. package/src/core/cards/thunderclock_card.cpp +237 -0
  78. package/src/core/cards/thunderclock_card.hpp +122 -0
  79. package/src/core/cpu/cpu6502.cpp +1609 -0
  80. package/src/core/cpu/cpu6502.hpp +203 -0
  81. package/src/core/debug/condition_evaluator.cpp +470 -0
  82. package/src/core/debug/condition_evaluator.hpp +87 -0
  83. package/src/core/disassembler/disassembler.cpp +552 -0
  84. package/src/core/disassembler/disassembler.hpp +171 -0
  85. package/src/core/disk-image/disk_image.hpp +267 -0
  86. package/src/core/disk-image/dsk_disk_image.cpp +827 -0
  87. package/src/core/disk-image/dsk_disk_image.hpp +204 -0
  88. package/src/core/disk-image/gcr_encoding.cpp +147 -0
  89. package/src/core/disk-image/gcr_encoding.hpp +78 -0
  90. package/src/core/disk-image/woz_disk_image.cpp +1049 -0
  91. package/src/core/disk-image/woz_disk_image.hpp +343 -0
  92. package/src/core/emulator.cpp +2126 -0
  93. package/src/core/emulator.hpp +434 -0
  94. package/src/core/filesystem/dos33.cpp +178 -0
  95. package/src/core/filesystem/dos33.hpp +66 -0
  96. package/src/core/filesystem/pascal.cpp +262 -0
  97. package/src/core/filesystem/pascal.hpp +87 -0
  98. package/src/core/filesystem/prodos.cpp +369 -0
  99. package/src/core/filesystem/prodos.hpp +119 -0
  100. package/src/core/input/keyboard.cpp +227 -0
  101. package/src/core/input/keyboard.hpp +111 -0
  102. package/src/core/mmu/mmu.cpp +1387 -0
  103. package/src/core/mmu/mmu.hpp +236 -0
  104. package/src/core/types.hpp +196 -0
  105. package/src/core/video/video.cpp +680 -0
  106. package/src/core/video/video.hpp +156 -0
  107. package/src/css/assembler-editor.css +1617 -0
  108. package/src/css/base.css +470 -0
  109. package/src/css/basic-debugger.css +791 -0
  110. package/src/css/basic-editor.css +792 -0
  111. package/src/css/controls.css +783 -0
  112. package/src/css/cpu-debugger.css +1413 -0
  113. package/src/css/debug-base.css +160 -0
  114. package/src/css/debug-windows.css +6455 -0
  115. package/src/css/disk-drives.css +406 -0
  116. package/src/css/documentation.css +392 -0
  117. package/src/css/file-explorer.css +867 -0
  118. package/src/css/hard-drive.css +180 -0
  119. package/src/css/layout.css +217 -0
  120. package/src/css/memory-windows.css +798 -0
  121. package/src/css/modals.css +510 -0
  122. package/src/css/monitor.css +425 -0
  123. package/src/css/release-notes.css +101 -0
  124. package/src/css/responsive.css +400 -0
  125. package/src/css/rule-builder.css +340 -0
  126. package/src/css/save-states.css +201 -0
  127. package/src/css/settings-windows.css +1231 -0
  128. package/src/css/window-switcher.css +150 -0
  129. package/src/js/agent/agent-manager.js +643 -0
  130. package/src/js/agent/agent-tools.js +293 -0
  131. package/src/js/agent/agent-version-tools.js +131 -0
  132. package/src/js/agent/assembler-tools.js +357 -0
  133. package/src/js/agent/basic-program-tools.js +894 -0
  134. package/src/js/agent/disk-tools.js +417 -0
  135. package/src/js/agent/file-explorer-tools.js +269 -0
  136. package/src/js/agent/index.js +13 -0
  137. package/src/js/agent/main-tools.js +222 -0
  138. package/src/js/agent/slot-tools.js +303 -0
  139. package/src/js/agent/smartport-tools.js +257 -0
  140. package/src/js/agent/window-tools.js +80 -0
  141. package/src/js/audio/audio-driver.js +417 -0
  142. package/src/js/audio/audio-worklet.js +85 -0
  143. package/src/js/audio/index.js +8 -0
  144. package/src/js/config/default-layout.js +34 -0
  145. package/src/js/config/version.js +8 -0
  146. package/src/js/data/apple2-rom-routines.js +577 -0
  147. package/src/js/debug/assembler-editor-window.js +2993 -0
  148. package/src/js/debug/basic-breakpoint-manager.js +529 -0
  149. package/src/js/debug/basic-program-parser.js +436 -0
  150. package/src/js/debug/basic-program-window.js +2594 -0
  151. package/src/js/debug/basic-variable-inspector.js +447 -0
  152. package/src/js/debug/breakpoint-manager.js +472 -0
  153. package/src/js/debug/cpu-debugger-window.js +2396 -0
  154. package/src/js/debug/index.js +22 -0
  155. package/src/js/debug/label-manager.js +238 -0
  156. package/src/js/debug/memory-browser-window.js +416 -0
  157. package/src/js/debug/memory-heat-map-window.js +481 -0
  158. package/src/js/debug/memory-map-window.js +206 -0
  159. package/src/js/debug/mockingboard-window.js +882 -0
  160. package/src/js/debug/mouse-card-window.js +355 -0
  161. package/src/js/debug/rule-builder-window.js +648 -0
  162. package/src/js/debug/soft-switch-window.js +458 -0
  163. package/src/js/debug/stack-viewer-window.js +221 -0
  164. package/src/js/debug/symbols.js +416 -0
  165. package/src/js/debug/trace-panel.js +291 -0
  166. package/src/js/debug/zero-page-watch-window.js +297 -0
  167. package/src/js/disk-manager/disk-drives-window.js +212 -0
  168. package/src/js/disk-manager/disk-operations.js +284 -0
  169. package/src/js/disk-manager/disk-persistence.js +301 -0
  170. package/src/js/disk-manager/disk-surface-renderer.js +388 -0
  171. package/src/js/disk-manager/drive-sounds.js +139 -0
  172. package/src/js/disk-manager/hard-drive-manager.js +481 -0
  173. package/src/js/disk-manager/hard-drive-persistence.js +187 -0
  174. package/src/js/disk-manager/hard-drive-window.js +57 -0
  175. package/src/js/disk-manager/index.js +890 -0
  176. package/src/js/display/display-settings-window.js +383 -0
  177. package/src/js/display/index.js +10 -0
  178. package/src/js/display/screen-window.js +342 -0
  179. package/src/js/display/webgl-renderer.js +705 -0
  180. package/src/js/file-explorer/disassembler.js +574 -0
  181. package/src/js/file-explorer/dos33.js +266 -0
  182. package/src/js/file-explorer/file-viewer.js +359 -0
  183. package/src/js/file-explorer/index.js +1261 -0
  184. package/src/js/file-explorer/prodos.js +549 -0
  185. package/src/js/file-explorer/utils.js +67 -0
  186. package/src/js/help/documentation-window.js +1096 -0
  187. package/src/js/help/index.js +10 -0
  188. package/src/js/help/release-notes-window.js +85 -0
  189. package/src/js/help/release-notes.js +612 -0
  190. package/src/js/input/gamepad-handler.js +176 -0
  191. package/src/js/input/index.js +12 -0
  192. package/src/js/input/input-handler.js +396 -0
  193. package/src/js/input/joystick-window.js +404 -0
  194. package/src/js/input/mouse-handler.js +99 -0
  195. package/src/js/input/text-selection.js +462 -0
  196. package/src/js/main.js +653 -0
  197. package/src/js/state/index.js +15 -0
  198. package/src/js/state/save-states-window.js +393 -0
  199. package/src/js/state/state-manager.js +409 -0
  200. package/src/js/state/state-persistence.js +218 -0
  201. package/src/js/ui/confirm.js +43 -0
  202. package/src/js/ui/disk-drive-positioner.js +347 -0
  203. package/src/js/ui/reminder-controller.js +129 -0
  204. package/src/js/ui/slot-configuration-window.js +560 -0
  205. package/src/js/ui/theme-manager.js +61 -0
  206. package/src/js/ui/toast.js +44 -0
  207. package/src/js/ui/ui-controller.js +897 -0
  208. package/src/js/ui/window-switcher.js +275 -0
  209. package/src/js/utils/basic-autocomplete.js +832 -0
  210. package/src/js/utils/basic-highlighting.js +473 -0
  211. package/src/js/utils/basic-tokenizer.js +153 -0
  212. package/src/js/utils/basic-tokens.js +117 -0
  213. package/src/js/utils/constants.js +28 -0
  214. package/src/js/utils/indexeddb-helper.js +225 -0
  215. package/src/js/utils/merlin-editor-support.js +905 -0
  216. package/src/js/utils/merlin-highlighting.js +551 -0
  217. package/src/js/utils/storage.js +125 -0
  218. package/src/js/utils/string-utils.js +19 -0
  219. package/src/js/utils/wasm-memory.js +54 -0
  220. package/src/js/windows/base-window.js +690 -0
  221. package/src/js/windows/index.js +9 -0
  222. package/src/js/windows/window-manager.js +375 -0
  223. package/tests/catch2/catch.hpp +17976 -0
  224. package/tests/common/basic_program_builder.cpp +119 -0
  225. package/tests/common/basic_program_builder.hpp +209 -0
  226. package/tests/common/disk_image_builder.cpp +444 -0
  227. package/tests/common/disk_image_builder.hpp +141 -0
  228. package/tests/common/test_helpers.hpp +118 -0
  229. package/tests/gcr/gcr-test.cpp +142 -0
  230. package/tests/integration/check-rom.js +70 -0
  231. package/tests/integration/compare-boot.js +239 -0
  232. package/tests/integration/crash-trace.js +102 -0
  233. package/tests/integration/disk-boot-test.js +264 -0
  234. package/tests/integration/memory-crash.js +108 -0
  235. package/tests/integration/nibble-read-test.js +249 -0
  236. package/tests/integration/phase-test.js +159 -0
  237. package/tests/integration/test_emulator.cpp +291 -0
  238. package/tests/integration/test_emulator_basic.cpp +91 -0
  239. package/tests/integration/test_emulator_debug.cpp +344 -0
  240. package/tests/integration/test_emulator_disk.cpp +153 -0
  241. package/tests/integration/test_emulator_state.cpp +163 -0
  242. package/tests/klaus/6502_functional_test.bin +0 -0
  243. package/tests/klaus/65C02_extended_opcodes_test.bin +0 -0
  244. package/tests/klaus/klaus_6502_test.cpp +184 -0
  245. package/tests/klaus/klaus_65c02_test.cpp +197 -0
  246. package/tests/thunderclock/thunderclock_mmu_test.cpp +304 -0
  247. package/tests/thunderclock/thunderclock_test.cpp +550 -0
  248. package/tests/unit/test_assembler.cpp +521 -0
  249. package/tests/unit/test_audio.cpp +196 -0
  250. package/tests/unit/test_ay8910.cpp +311 -0
  251. package/tests/unit/test_basic_detokenizer.cpp +265 -0
  252. package/tests/unit/test_basic_tokenizer.cpp +382 -0
  253. package/tests/unit/test_block_device.cpp +259 -0
  254. package/tests/unit/test_condition_evaluator.cpp +219 -0
  255. package/tests/unit/test_cpu6502.cpp +1301 -0
  256. package/tests/unit/test_cpu_addressing.cpp +361 -0
  257. package/tests/unit/test_cpu_cycle_counts.cpp +409 -0
  258. package/tests/unit/test_cpu_decimal.cpp +166 -0
  259. package/tests/unit/test_cpu_interrupts.cpp +285 -0
  260. package/tests/unit/test_disassembler.cpp +323 -0
  261. package/tests/unit/test_disk2_card.cpp +330 -0
  262. package/tests/unit/test_dos33.cpp +273 -0
  263. package/tests/unit/test_dsk_disk_image.cpp +315 -0
  264. package/tests/unit/test_expansion_card.cpp +178 -0
  265. package/tests/unit/test_gcr_encoding.cpp +232 -0
  266. package/tests/unit/test_keyboard.cpp +262 -0
  267. package/tests/unit/test_mmu.cpp +555 -0
  268. package/tests/unit/test_mmu_slots.cpp +323 -0
  269. package/tests/unit/test_mockingboard.cpp +352 -0
  270. package/tests/unit/test_mouse_card.cpp +386 -0
  271. package/tests/unit/test_pascal.cpp +248 -0
  272. package/tests/unit/test_prodos.cpp +259 -0
  273. package/tests/unit/test_smartport_card.cpp +321 -0
  274. package/tests/unit/test_thunderclock.cpp +354 -0
  275. package/tests/unit/test_via6522.cpp +323 -0
  276. package/tests/unit/test_video.cpp +319 -0
  277. package/tests/unit/test_woz_disk_image.cpp +257 -0
  278. package/vite.config.js +96 -0
  279. package/wiki/AI-Agent.md +372 -0
  280. package/wiki/Architecture-Overview.md +303 -0
  281. package/wiki/Audio-System.md +449 -0
  282. package/wiki/CPU-Emulation.md +477 -0
  283. package/wiki/Debugger.md +516 -0
  284. package/wiki/Disk-Drives.md +161 -0
  285. package/wiki/Disk-System-Internals.md +547 -0
  286. package/wiki/Display-Settings.md +88 -0
  287. package/wiki/Expansion-Slots.md +187 -0
  288. package/wiki/File-Explorer.md +259 -0
  289. package/wiki/Getting-Started.md +156 -0
  290. package/wiki/Home.md +69 -0
  291. package/wiki/Input-Devices.md +183 -0
  292. package/wiki/Keyboard-Shortcuts.md +158 -0
  293. package/wiki/Memory-System.md +364 -0
  294. package/wiki/Save-States.md +172 -0
  295. package/wiki/Video-Rendering.md +658 -0
@@ -0,0 +1,264 @@
1
+ /**
2
+ * Headless Disk Boot Test
3
+ *
4
+ * This test loads the DOS 3.3 disk image and runs the emulator for enough
5
+ * cycles to boot, then checks if "DOS" appears in screen memory.
6
+ *
7
+ * Apple II text screen memory is at $0400-$07FF
8
+ * The screen is 40x24 characters, but the memory is not linear.
9
+ *
10
+ * Run with: node test/disk-boot-test.js
11
+ */
12
+
13
+ import { readFileSync } from 'fs';
14
+ import { fileURLToPath } from 'url';
15
+ import { dirname, join, resolve } from 'path';
16
+ import { createContext, runInContext } from 'vm';
17
+
18
+ const __filename = fileURLToPath(import.meta.url);
19
+ const __dirname = dirname(__filename);
20
+
21
+ /**
22
+ * Load the WASM module using vm context (needed because of ES module compatibility)
23
+ */
24
+ async function loadWasmModule() {
25
+ const wasmJsPath = join(__dirname, '..', 'public', 'a2e.js');
26
+ const wasmDir = dirname(wasmJsPath);
27
+
28
+ const code = readFileSync(wasmJsPath, 'utf8');
29
+
30
+ // Create a context with the necessary globals for the WASM module
31
+ const context = {
32
+ module: { exports: {} },
33
+ exports: {},
34
+ globalThis: global,
35
+ console: console,
36
+ require: (await import('module')).createRequire(import.meta.url),
37
+ __dirname: wasmDir,
38
+ __filename: wasmJsPath,
39
+ process: process,
40
+ WebAssembly: WebAssembly,
41
+ URL: URL,
42
+ TextDecoder: TextDecoder,
43
+ setTimeout: setTimeout,
44
+ clearTimeout: clearTimeout,
45
+ performance: { now: () => Date.now() }
46
+ };
47
+
48
+ createContext(context);
49
+ runInContext(code, context);
50
+
51
+ return context.module.exports;
52
+ }
53
+
54
+ async function runTest() {
55
+ console.log('Loading WASM module...');
56
+
57
+ const createA2EModule = await loadWasmModule();
58
+
59
+ if (typeof createA2EModule !== 'function') {
60
+ console.error('Failed to load WASM module factory');
61
+ process.exit(1);
62
+ }
63
+
64
+ // Create the module instance
65
+ const Module = await createA2EModule();
66
+
67
+ console.log('Initializing emulator...');
68
+ Module._init();
69
+
70
+ // Load the disk image
71
+ const diskPath = join(__dirname, '..', 'public', 'Apple DOS 3.3 August 1980.dsk');
72
+ console.log(`Loading disk image: ${diskPath}`);
73
+
74
+ const diskData = readFileSync(diskPath);
75
+ console.log(`Disk size: ${diskData.length} bytes`);
76
+
77
+ // Allocate memory for disk data
78
+ const diskPtr = Module._malloc(diskData.length);
79
+ Module.HEAPU8.set(diskData, diskPtr);
80
+
81
+ // Allocate memory for filename
82
+ const filename = 'Apple DOS 3.3 August 1980.dsk';
83
+ const filenamePtr = Module._malloc(filename.length + 1);
84
+ Module.stringToUTF8(filename, filenamePtr, filename.length + 1);
85
+
86
+ // Insert the disk
87
+ const inserted = Module._insertDisk(0, diskPtr, diskData.length, filenamePtr);
88
+ console.log(`Disk inserted: ${inserted}`);
89
+
90
+ // Free the filename memory
91
+ Module._free(filenamePtr);
92
+
93
+ if (!inserted) {
94
+ console.error('Failed to insert disk!');
95
+ process.exit(1);
96
+ }
97
+
98
+ // Run emulation for enough cycles to boot
99
+ // DOS 3.3 boot takes about 2-3 seconds at 1.023 MHz
100
+ // That's roughly 2-3 million cycles
101
+ const CYCLES_PER_BATCH = 100000;
102
+ const MAX_BATCHES = 200; // ~20 million cycles (about 20 seconds of emulation)
103
+
104
+ console.log('Running emulation...');
105
+
106
+ let lastTrack = -1;
107
+ let lastByte = 0;
108
+ let readCount = 0;
109
+
110
+ for (let batch = 0; batch < MAX_BATCHES; batch++) {
111
+ Module._runCycles(CYCLES_PER_BATCH);
112
+
113
+ // Check disk state
114
+ const track = Module._getDiskTrack(0);
115
+ const motorOn = Module._getDiskMotorOn(0);
116
+ const lastDiskByte = Module._getLastDiskByte();
117
+
118
+ if (track !== lastTrack) {
119
+ console.log(`\n Track changed: ${lastTrack} -> ${track}`);
120
+ lastTrack = track;
121
+ }
122
+
123
+ if (lastDiskByte !== lastByte) {
124
+ readCount++;
125
+ if (readCount <= 20) {
126
+ console.log(` Disk byte: $${lastDiskByte.toString(16).toUpperCase().padStart(2, '0')} (track ${track}, motor: ${motorOn})`);
127
+ }
128
+ lastByte = lastDiskByte;
129
+ }
130
+
131
+ // Check for DOS in screen memory every batch
132
+ const found = checkForDOS(Module);
133
+ if (found) {
134
+ const totalCycles = Module._getTotalCycles();
135
+ console.log(`\nSUCCESS: Found "DOS" in screen memory after ${totalCycles} cycles!`);
136
+ dumpScreenMemory(Module);
137
+ Module._free(diskPtr);
138
+ process.exit(0);
139
+ }
140
+
141
+ // Progress indicator
142
+ if (batch % 10 === 0) {
143
+ const totalCycles = Module._getTotalCycles();
144
+ const pc = Module._getPC();
145
+ process.stdout.write(`\rCycles: ${totalCycles}, PC: $${pc.toString(16).toUpperCase().padStart(4, '0')}, Track: ${track}, Motor: ${motorOn}`);
146
+ }
147
+ }
148
+
149
+ console.log('\n\nFAILED: Did not find "DOS" in screen memory');
150
+ console.log('\nScreen memory dump:');
151
+ dumpScreenMemory(Module);
152
+
153
+ // Also dump some debug info
154
+ console.log('\nDebug info:');
155
+ console.log(`PC: $${Module._getPC().toString(16).toUpperCase().padStart(4, '0')}`);
156
+ console.log(`A: $${Module._getA().toString(16).toUpperCase().padStart(2, '0')}`);
157
+ console.log(`X: $${Module._getX().toString(16).toUpperCase().padStart(2, '0')}`);
158
+ console.log(`Y: $${Module._getY().toString(16).toUpperCase().padStart(2, '0')}`);
159
+ console.log(`SP: $${Module._getSP().toString(16).toUpperCase().padStart(2, '0')}`);
160
+ console.log(`Total cycles: ${Module._getTotalCycles()}`);
161
+
162
+ // Dump zero page
163
+ console.log('\nZero page ($00-$3F):');
164
+ let zpLine = '';
165
+ for (let i = 0; i < 0x40; i++) {
166
+ if (i % 16 === 0) {
167
+ if (zpLine) console.log(zpLine);
168
+ zpLine = `$${i.toString(16).toUpperCase().padStart(2, '0')}: `;
169
+ }
170
+ zpLine += Module._readMemory(i).toString(16).toUpperCase().padStart(2, '0') + ' ';
171
+ }
172
+ console.log(zpLine);
173
+
174
+ // Dump boot sector area ($0800-$08FF)
175
+ console.log('\nBoot sector memory ($0800-$08FF):');
176
+ for (let row = 0; row < 16; row++) {
177
+ const addr = 0x0800 + row * 16;
178
+ let line = `$${addr.toString(16).toUpperCase().padStart(4, '0')}: `;
179
+ let ascii = '';
180
+ for (let col = 0; col < 16; col++) {
181
+ const byte = Module._readMemory(addr + col);
182
+ line += byte.toString(16).toUpperCase().padStart(2, '0') + ' ';
183
+ ascii += (byte >= 0x20 && byte < 0x7F) ? String.fromCharCode(byte) : '.';
184
+ }
185
+ console.log(line + ' ' + ascii);
186
+ }
187
+
188
+ Module._free(diskPtr);
189
+ process.exit(1);
190
+ }
191
+
192
+ /**
193
+ * Check if "DOS" appears in screen memory
194
+ * Apple II screen memory is at $0400-$07FF
195
+ */
196
+ function checkForDOS(Module) {
197
+ // Apple II character codes for "DOS" (high bit may be set)
198
+ // D = 0x44 or 0xC4, O = 0x4F or 0xCF, S = 0x53 or 0xD3
199
+
200
+ // Scan all of screen memory
201
+ for (let addr = 0x0400; addr < 0x0800 - 2; addr++) {
202
+ const c1 = Module._readMemory(addr) & 0x7F;
203
+ const c2 = Module._readMemory(addr + 1) & 0x7F;
204
+ const c3 = Module._readMemory(addr + 2) & 0x7F;
205
+
206
+ if (c1 === 0x44 && c2 === 0x4F && c3 === 0x53) { // "DOS"
207
+ return true;
208
+ }
209
+ }
210
+
211
+ return false;
212
+ }
213
+
214
+ /**
215
+ * Dump screen memory as text
216
+ */
217
+ function dumpScreenMemory(Module) {
218
+ // Apple II screen memory layout is complex
219
+ // Each row is split into 3 groups of 8 rows
220
+ const rowBases = [
221
+ 0x0400, 0x0480, 0x0500, 0x0580, 0x0600, 0x0680, 0x0700, 0x0780,
222
+ 0x0428, 0x04A8, 0x0528, 0x05A8, 0x0628, 0x06A8, 0x0728, 0x07A8,
223
+ 0x0450, 0x04D0, 0x0550, 0x05D0, 0x0650, 0x06D0, 0x0750, 0x07D0
224
+ ];
225
+
226
+ console.log('┌────────────────────────────────────────┐');
227
+
228
+ for (let row = 0; row < 24; row++) {
229
+ let line = '│';
230
+ const base = rowBases[row];
231
+
232
+ for (let col = 0; col < 40; col++) {
233
+ const char = Module._readMemory(base + col);
234
+ const ascii = appleCharToAscii(char);
235
+ line += ascii;
236
+ }
237
+
238
+ console.log(line + '│');
239
+ }
240
+
241
+ console.log('└────────────────────────────────────────┘');
242
+ }
243
+
244
+ /**
245
+ * Convert Apple II character code to ASCII
246
+ */
247
+ function appleCharToAscii(char) {
248
+ const c = char & 0x7F;
249
+
250
+ if (c >= 0x20 && c < 0x60) {
251
+ return String.fromCharCode(c);
252
+ } else if (c >= 0x00 && c < 0x20) {
253
+ return String.fromCharCode(c + 0x40);
254
+ } else if (c >= 0x60) {
255
+ return String.fromCharCode(c);
256
+ }
257
+
258
+ return ' ';
259
+ }
260
+
261
+ runTest().catch(err => {
262
+ console.error('Test error:', err);
263
+ process.exit(1);
264
+ });
@@ -0,0 +1,108 @@
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('=== MEMORY STATE TEST ===\n');
50
+
51
+ // Check ROM at startup
52
+ console.log('At startup:');
53
+ console.log(' $FE89 = $' + Module._readMemory(0xFE89).toString(16).toUpperCase());
54
+ console.log(' $FE8A = $' + Module._readMemory(0xFE8A).toString(16).toUpperCase());
55
+ console.log(' $FE8B = $' + Module._readMemory(0xFE8B).toString(16).toUpperCase());
56
+ console.log(' Soft switches: 0x' + Module._getSoftSwitchState().toString(16).toUpperCase());
57
+
58
+ // Run to near crash point
59
+ console.log('\nRunning to 4,050,000 cycles...');
60
+ while (Module._getTotalCycles() < 4050000) {
61
+ Module._runCycles(1000);
62
+ }
63
+
64
+ // Check memory state just before crash
65
+ console.log('\nAt cycle ' + Module._getTotalCycles() + ':');
66
+ console.log(' $FE89 = $' + Module._readMemory(0xFE89).toString(16).toUpperCase());
67
+ console.log(' $FE8A = $' + Module._readMemory(0xFE8A).toString(16).toUpperCase());
68
+ console.log(' $FE8B = $' + Module._readMemory(0xFE8B).toString(16).toUpperCase());
69
+ console.log(' Soft switches: 0x' + Module._getSoftSwitchState().toString(16).toUpperCase());
70
+
71
+ // Step until crash
72
+ for (let i = 0; i < 500000; i++) {
73
+ const pc = Module._getPC();
74
+
75
+ // Check memory periodically
76
+ if (i % 10000 === 0) {
77
+ const fe89 = Module._readMemory(0xFE89);
78
+ if (fe89 !== 0xA9) {
79
+ console.log('\n*** $FE89 changed from $A9 to $' + fe89.toString(16).toUpperCase() + ' at cycle ' + Module._getTotalCycles() + ' ***');
80
+ console.log(' PC = $' + pc.toString(16).toUpperCase());
81
+ console.log(' Soft switches: 0x' + Module._getSoftSwitchState().toString(16).toUpperCase());
82
+ }
83
+ }
84
+
85
+ Module._stepInstruction();
86
+
87
+ const newPC = Module._getPC();
88
+ if (newPC === 0) {
89
+ console.log('\n*** CRASH at cycle ' + Module._getTotalCycles() + ' ***');
90
+ console.log(' Last PC was $' + pc.toString(16).toUpperCase());
91
+ console.log(' $FE89 at crash: $' + Module._readMemory(0xFE89).toString(16).toUpperCase());
92
+ console.log(' Soft switches at crash: 0x' + Module._getSoftSwitchState().toString(16).toUpperCase());
93
+
94
+ // Check what JSR target would be
95
+ if (pc >= 0x3740 && pc <= 0x3750) {
96
+ console.log(' Memory at $3744-$3746: $' +
97
+ Module._readMemory(0x3744).toString(16).padStart(2,'0') + ' ' +
98
+ Module._readMemory(0x3745).toString(16).padStart(2,'0') + ' ' +
99
+ Module._readMemory(0x3746).toString(16).padStart(2,'0'));
100
+ }
101
+ break;
102
+ }
103
+ }
104
+
105
+ Module._free(diskPtr);
106
+ }
107
+
108
+ runTest().catch(err => { console.error(err); process.exit(1); });
@@ -0,0 +1,249 @@
1
+ /**
2
+ * Nibble Read Test - Track actual disk reads during boot
3
+ */
4
+
5
+ import { readFileSync } from 'fs';
6
+ import { fileURLToPath } from 'url';
7
+ import { dirname, join } from 'path';
8
+ import { createContext, runInContext } from 'vm';
9
+
10
+ const __filename = fileURLToPath(import.meta.url);
11
+ const __dirname = dirname(__filename);
12
+
13
+ async function loadWasmModule() {
14
+ const wasmJsPath = join(__dirname, '..', 'public', 'a2e.js');
15
+ const wasmDir = dirname(wasmJsPath);
16
+ const code = readFileSync(wasmJsPath, 'utf8');
17
+
18
+ const context = {
19
+ module: { exports: {} },
20
+ exports: {},
21
+ globalThis: global,
22
+ console: console,
23
+ require: (await import('module')).createRequire(import.meta.url),
24
+ __dirname: wasmDir,
25
+ __filename: wasmJsPath,
26
+ process: process,
27
+ WebAssembly: WebAssembly,
28
+ URL: URL,
29
+ TextDecoder: TextDecoder,
30
+ setTimeout: setTimeout,
31
+ clearTimeout: clearTimeout,
32
+ performance: { now: () => Date.now() }
33
+ };
34
+
35
+ createContext(context);
36
+ runInContext(code, context);
37
+ return context.module.exports;
38
+ }
39
+
40
+ async function runTest() {
41
+ console.log('Loading WASM module...');
42
+ const createA2EModule = await loadWasmModule();
43
+ const Module = await createA2EModule();
44
+
45
+ console.log('Initializing emulator...');
46
+ Module._init();
47
+
48
+ // Load disk
49
+ const diskPath = join(__dirname, '..', 'public', 'Apple DOS 3.3 August 1980.dsk');
50
+ const diskData = readFileSync(diskPath);
51
+ const diskPtr = Module._malloc(diskData.length);
52
+ Module.HEAPU8.set(diskData, diskPtr);
53
+ const filename = 'test.dsk';
54
+ const filenamePtr = Module._malloc(filename.length + 1);
55
+ Module.stringToUTF8(filename, filenamePtr, filename.length + 1);
56
+ Module._insertDisk(0, diskPtr, diskData.length, filenamePtr);
57
+ Module._free(filenamePtr);
58
+
59
+ // Get track 0 nibbles for comparison
60
+ const track0Nibbles = [];
61
+ const count = Module._getTrackNibbleCount(0, 0);
62
+ for (let i = 0; i < count; i++) {
63
+ track0Nibbles.push(Module._getTrackNibble(0, 0, i));
64
+ }
65
+
66
+ console.log('Track 0 has ' + count + ' nibbles');
67
+
68
+ // Find first sync run and address prologue
69
+ let syncEnd = 0;
70
+ for (let i = 0; i < count; i++) {
71
+ if (track0Nibbles[i] !== 0xFF) {
72
+ syncEnd = i;
73
+ break;
74
+ }
75
+ }
76
+ console.log('Sync bytes end at position ' + syncEnd);
77
+
78
+ let nibblesStr = '';
79
+ for (let i = syncEnd; i < syncEnd + 10; i++) {
80
+ nibblesStr += track0Nibbles[i].toString(16).toUpperCase().padStart(2, '0') + ' ';
81
+ }
82
+ console.log('Nibbles at sync end: ' + nibblesStr);
83
+
84
+ // Run to disk boot ROM
85
+ console.log('\nRunning to disk boot ROM...');
86
+ for (let i = 0; i < 2000; i++) {
87
+ Module._runCycles(1000);
88
+ const pc = Module._getPC();
89
+ if (pc >= 0xC600 && pc < 0xC700) {
90
+ console.log('Entered disk boot ROM at $' + pc.toString(16).toUpperCase() + ' after ' + Module._getTotalCycles() + ' cycles');
91
+ break;
92
+ }
93
+ }
94
+
95
+ // Track nibble reads during boot until screen text appears
96
+ console.log('\nTracking disk reads during boot (waiting for screen text)...');
97
+ let lastLatch = 0;
98
+ let lastPos = Module._getCurrentNibblePosition(0);
99
+ let lastTrack = Module._getDiskTrack(0);
100
+ const readLog = [];
101
+ let crashPC = 0;
102
+ let screenTextFound = false;
103
+
104
+ // Helper to check if screen has "DOS" text (indicates disk boot success)
105
+ function hasDOSText() {
106
+ // Look for "DOS" in screen memory
107
+ for (let addr = 0x0400; addr < 0x0800 - 2; addr++) {
108
+ const c1 = Module._readMemory(addr) & 0x7F;
109
+ const c2 = Module._readMemory(addr + 1) & 0x7F;
110
+ const c3 = Module._readMemory(addr + 2) & 0x7F;
111
+ if (c1 === 0x44 && c2 === 0x4F && c3 === 0x53) { // "DOS"
112
+ return true;
113
+ }
114
+ }
115
+ return false;
116
+ }
117
+
118
+ // Helper to dump screen contents
119
+ function dumpScreen() {
120
+ const rowBases = [
121
+ 0x0400, 0x0480, 0x0500, 0x0580, 0x0600, 0x0680, 0x0700, 0x0780,
122
+ 0x0428, 0x04A8, 0x0528, 0x05A8, 0x0628, 0x06A8, 0x0728, 0x07A8,
123
+ 0x0450, 0x04D0, 0x0550, 0x05D0, 0x0650, 0x06D0, 0x0750, 0x07D0
124
+ ];
125
+
126
+ console.log('Screen contents:');
127
+ for (let row = 0; row < 24; row++) {
128
+ let line = '';
129
+ const base = rowBases[row];
130
+ for (let col = 0; col < 40; col++) {
131
+ const c = Module._readMemory(base + col) & 0x7F;
132
+ if (c >= 0x20 && c < 0x7F) {
133
+ line += String.fromCharCode(c);
134
+ } else if (c >= 0x00 && c < 0x20) {
135
+ line += String.fromCharCode(c + 0x40);
136
+ } else {
137
+ line += ' ';
138
+ }
139
+ }
140
+ console.log('|' + line + '|');
141
+ }
142
+ }
143
+
144
+ const MAX_BATCHES = 50000; // Run for much longer
145
+ for (let batch = 0; batch < MAX_BATCHES; batch++) {
146
+ const pcBefore = Module._getPC();
147
+ Module._runCycles(100);
148
+ const pcAfter = Module._getPC();
149
+
150
+ const pos = Module._getCurrentNibblePosition(0);
151
+ const latch = Module._getLastDiskByte(0);
152
+ const track = Module._getDiskTrack(0);
153
+ const motor = Module._getDiskMotorOn(0);
154
+
155
+ // Detect crash
156
+ if (pcAfter === 0 && pcBefore !== 0 && pcBefore !== 0xFFFC) {
157
+ console.log('\n*** CRASH DETECTED at cycle ' + Module._getTotalCycles() + ' ***');
158
+ console.log('Previous PC: $' + pcBefore.toString(16).toUpperCase());
159
+ console.log('Current Track: ' + track + ', Motor: ' + motor);
160
+ dumpScreen();
161
+ crashPC = pcBefore;
162
+ break;
163
+ }
164
+
165
+ // Track nibble reads
166
+ if (latch !== lastLatch && motor) {
167
+ if (readLog.length < 500) {
168
+ // Track 0 comparison
169
+ let expectedNibble = 0;
170
+ if (track === 0) {
171
+ // Get what nibble should be at the PREVIOUS position (since readNibble advances)
172
+ const readPos = (pos - 1 + count) % count;
173
+ expectedNibble = track0Nibbles[readPos] | 0x80;
174
+ }
175
+
176
+ readLog.push({
177
+ cycle: Module._getTotalCycles(),
178
+ track: track,
179
+ pos: pos,
180
+ readPos: (pos - 1 + count) % count,
181
+ latch: latch,
182
+ expected: expectedNibble
183
+ });
184
+ }
185
+ lastLatch = latch;
186
+ }
187
+
188
+ lastPos = pos;
189
+ lastTrack = track;
190
+
191
+ // Check for DOS text every 1000 batches
192
+ if (batch % 1000 === 0) {
193
+ if (hasDOSText()) {
194
+ console.log('\nDOS text detected after ' + Module._getTotalCycles() + ' cycles!');
195
+ dumpScreen();
196
+ screenTextFound = true;
197
+ break;
198
+ }
199
+ // Progress indicator
200
+ process.stdout.write('\rCycles: ' + Module._getTotalCycles() + ', Track: ' + track);
201
+ }
202
+ }
203
+
204
+ if (!screenTextFound && crashPC === 0) {
205
+ console.log('\nTimeout: No screen text after ' + Module._getTotalCycles() + ' cycles');
206
+ }
207
+
208
+ console.log('\nCaptured ' + readLog.length + ' disk byte changes');
209
+
210
+ // Show first 30 reads
211
+ console.log('\nFirst 30 disk byte changes:');
212
+ for (let i = 0; i < Math.min(30, readLog.length); i++) {
213
+ const r = readLog[i];
214
+ let status = '';
215
+ if (r.track === 0 && r.expected !== 0) {
216
+ status = r.latch === r.expected ? ' OK' : ' MISMATCH (expected $' + r.expected.toString(16).toUpperCase() + ')';
217
+ }
218
+ console.log(' cycle=' + r.cycle.toString().padStart(8) + ' track=' + r.track + ' pos=' + r.readPos.toString().padStart(4) + ' latch=$' + r.latch.toString(16).toUpperCase().padStart(2, '0') + status);
219
+ }
220
+
221
+ // Check for mismatches on track 0
222
+ const track0Reads = readLog.filter(r => r.track === 0 && r.expected !== 0);
223
+ const mismatches = track0Reads.filter(r => r.latch !== r.expected);
224
+
225
+ if (mismatches.length > 0) {
226
+ console.log('\n*** Found ' + mismatches.length + ' mismatches on track 0! ***');
227
+ for (let i = 0; i < Math.min(10, mismatches.length); i++) {
228
+ const m = mismatches[i];
229
+ console.log(' pos=' + m.readPos + ': expected=$' + m.expected.toString(16).toUpperCase() + ' actual=$' + m.latch.toString(16).toUpperCase());
230
+ }
231
+ } else if (track0Reads.length > 0) {
232
+ console.log('\nAll ' + track0Reads.length + ' track 0 nibble reads matched!');
233
+ }
234
+
235
+ // Look for patterns in the read positions
236
+ console.log('\nNibble position progression (first 50):');
237
+ const positions = [];
238
+ for (let i = 0; i < Math.min(50, readLog.length); i++) {
239
+ positions.push(readLog[i].readPos);
240
+ }
241
+ console.log(positions.join(' -> '));
242
+
243
+ Module._free(diskPtr);
244
+ }
245
+
246
+ runTest().catch(err => {
247
+ console.error('Error:', err);
248
+ process.exit(1);
249
+ });