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,222 @@
1
+ /*
2
+ * main-tools.js - Main emulator control tools
3
+ *
4
+ * Written by
5
+ * Shawn Bullock <shawn@agenticexpert.ai>
6
+ */
7
+
8
+ /**
9
+ * Parse address or length value from hex ($xxxx) or decimal format
10
+ * @param {string|number} value - Value to parse
11
+ * @param {string} paramName - Parameter name for error messages
12
+ * @returns {number} Parsed integer value
13
+ */
14
+ function parseHexOrDecimal(value, paramName) {
15
+ if (value === undefined || value === null) {
16
+ throw new Error(`${paramName} parameter is required`);
17
+ }
18
+
19
+ // If already a number, use it directly
20
+ if (typeof value === "number") {
21
+ return Math.floor(value);
22
+ }
23
+
24
+ // If string, check for hex prefix
25
+ if (typeof value === "string") {
26
+ const trimmed = value.trim();
27
+
28
+ // Hex format: $xxxx or 0xXXXX
29
+ if (trimmed.startsWith("$")) {
30
+ const parsed = parseInt(trimmed.substring(1), 16);
31
+ if (isNaN(parsed)) {
32
+ throw new Error(`Invalid hex ${paramName}: ${value}`);
33
+ }
34
+ return parsed;
35
+ }
36
+
37
+ if (trimmed.toLowerCase().startsWith("0x")) {
38
+ const parsed = parseInt(trimmed, 16);
39
+ if (isNaN(parsed)) {
40
+ throw new Error(`Invalid hex ${paramName}: ${value}`);
41
+ }
42
+ return parsed;
43
+ }
44
+
45
+ // Decimal format
46
+ const parsed = parseInt(trimmed, 10);
47
+ if (isNaN(parsed)) {
48
+ throw new Error(`Invalid decimal ${paramName}: ${value}`);
49
+ }
50
+ return parsed;
51
+ }
52
+
53
+ throw new Error(`${paramName} must be a number or string`);
54
+ }
55
+
56
+ export const mainTools = {
57
+ /**
58
+ * Power control
59
+ */
60
+ emulatorPower: async (args) => {
61
+ const { action = "toggle" } = args;
62
+
63
+ const emulator = window.emulator;
64
+ if (!emulator) {
65
+ throw new Error("Emulator not available");
66
+ }
67
+
68
+ if (action === "on" && !emulator.running) {
69
+ emulator.start();
70
+ } else if (action === "off" && emulator.running) {
71
+ emulator.stop();
72
+ } else if (action === "toggle") {
73
+ if (emulator.running) {
74
+ emulator.stop();
75
+ } else {
76
+ emulator.start();
77
+ }
78
+ }
79
+
80
+ return {
81
+ success: true,
82
+ running: emulator.running,
83
+ message: `Emulator is now ${emulator.running ? "running" : "stopped"}`,
84
+ };
85
+ },
86
+
87
+ /**
88
+ * Ctrl-Reset (warm reset)
89
+ */
90
+ emulatorCtrlReset: async (args) => {
91
+ const wasmModule = window.emulator?.wasmModule;
92
+ if (!wasmModule) {
93
+ throw new Error("Emulator not available");
94
+ }
95
+
96
+ wasmModule._warmReset();
97
+
98
+ return {
99
+ success: true,
100
+ message: "Ctrl-Reset executed (warm reset)",
101
+ };
102
+ },
103
+
104
+ /**
105
+ * Reboot (cold reset)
106
+ */
107
+ emulatorReboot: async (args) => {
108
+ const wasmModule = window.emulator?.wasmModule;
109
+ if (!wasmModule) {
110
+ throw new Error("Emulator not available");
111
+ }
112
+
113
+ wasmModule._reset();
114
+
115
+ return {
116
+ success: true,
117
+ message: "Reboot executed (cold reset)",
118
+ };
119
+ },
120
+
121
+ /**
122
+ * Load binary data into memory at a specific address
123
+ */
124
+ directLoadBinaryAt: async (args) => {
125
+ const { address, contentBase64 } = args;
126
+
127
+ if (!contentBase64) {
128
+ throw new Error("contentBase64 parameter is required");
129
+ }
130
+
131
+ const wasmModule = window.emulator?.wasmModule;
132
+ if (!wasmModule) {
133
+ throw new Error("WASM module not available");
134
+ }
135
+
136
+ // Parse address (supports $xxxx hex or decimal)
137
+ const addr = parseHexOrDecimal(address, "address");
138
+
139
+ // Decode base64 to binary
140
+ const binaryString = atob(contentBase64);
141
+ const bytes = new Uint8Array(binaryString.length);
142
+ for (let i = 0; i < binaryString.length; i++) {
143
+ bytes[i] = binaryString.charCodeAt(i);
144
+ }
145
+
146
+ // Pause emulator while writing to ensure clean state
147
+ const wasPaused = wasmModule._isPaused();
148
+ wasmModule._setPaused(true);
149
+
150
+ // Write bytes using writeMemory (like assembler does)
151
+ for (let i = 0; i < bytes.length; i++) {
152
+ wasmModule._writeMemory((addr + i) & 0xffff, bytes[i]);
153
+ }
154
+
155
+ // Restore paused state
156
+ wasmModule._setPaused(wasPaused);
157
+
158
+ const addrHex = "$" + addr.toString(16).toUpperCase().padStart(4, "0");
159
+ const endAddr = (addr + bytes.length - 1) & 0xffff;
160
+ const endHex = "$" + endAddr.toString(16).toUpperCase().padStart(4, "0");
161
+
162
+ return {
163
+ success: true,
164
+ address: addr,
165
+ addressHex: addrHex,
166
+ size: bytes.length,
167
+ endAddress: endAddr,
168
+ endAddressHex: endHex,
169
+ message: `Loaded ${bytes.length} bytes to ${addrHex}-${endHex}`,
170
+ };
171
+ },
172
+
173
+ /**
174
+ * Save binary data from memory range to base64
175
+ */
176
+ directSaveBinaryRangeTo: async (args) => {
177
+ const { address, length } = args;
178
+
179
+ const wasmModule = window.emulator?.wasmModule;
180
+ if (!wasmModule) {
181
+ throw new Error("WASM module not available");
182
+ }
183
+
184
+ // Parse address and length (supports $xxxx hex or decimal)
185
+ const addr = parseHexOrDecimal(address, "address");
186
+ const len = parseHexOrDecimal(length, "length");
187
+
188
+ if (len <= 0) {
189
+ throw new Error("length must be > 0");
190
+ }
191
+
192
+ // Read bytes using peekMemory (no side effects)
193
+ const bytes = new Uint8Array(len);
194
+ for (let i = 0; i < len; i++) {
195
+ bytes[i] = wasmModule._peekMemory((addr + i) & 0xffff);
196
+ }
197
+
198
+ // Encode to base64
199
+ let binaryString = "";
200
+ for (let i = 0; i < bytes.length; i++) {
201
+ binaryString += String.fromCharCode(bytes[i]);
202
+ }
203
+ const contentBase64 = btoa(binaryString);
204
+
205
+ const addrHex = "$" + addr.toString(16).toUpperCase().padStart(4, "0");
206
+ const lengthHex = "$" + len.toString(16).toUpperCase().padStart(4, "0");
207
+ const endAddr = (addr + len - 1) & 0xffff;
208
+ const endHex = "$" + endAddr.toString(16).toUpperCase().padStart(4, "0");
209
+
210
+ return {
211
+ success: true,
212
+ address: addr,
213
+ addressHex: addrHex,
214
+ length: len,
215
+ lengthHex: lengthHex,
216
+ endAddress: endAddr,
217
+ endAddressHex: endHex,
218
+ contentBase64: contentBase64,
219
+ message: `Read ${len} bytes (${lengthHex}) from ${addrHex}-${endHex}`,
220
+ };
221
+ },
222
+ };
@@ -0,0 +1,303 @@
1
+ /*
2
+ * slot-tools.js - Expansion slot management tools
3
+ *
4
+ * Written by
5
+ * Shawn Bullock <shawn@agenticexpert.ai>
6
+ */
7
+
8
+ // Slot metadata (matches slot-configuration-window.js)
9
+ const SLOT_CONFIG = [
10
+ { slot: 1, compatible: [], fixed: false },
11
+ { slot: 2, compatible: ["smartport"], fixed: false },
12
+ { slot: 3, compatible: [], fixed: true },
13
+ { slot: 4, compatible: ["mockingboard", "mouse", "smartport"], fixed: false },
14
+ { slot: 5, compatible: ["thunderclock", "smartport"], fixed: false },
15
+ { slot: 6, compatible: ["disk2"], fixed: false },
16
+ { slot: 7, compatible: ["thunderclock", "smartport"], fixed: false },
17
+ ];
18
+
19
+ const ALL_CARDS = ["disk2", "mockingboard", "thunderclock", "mouse", "smartport"];
20
+
21
+ function getWasm() {
22
+ const wasmModule = window.emulator?.wasmModule;
23
+ if (!wasmModule) {
24
+ throw new Error("WASM module not available");
25
+ }
26
+ return wasmModule;
27
+ }
28
+
29
+ function getSlotCard(wasmModule, slot) {
30
+ const ptr = wasmModule._getSlotCard(slot);
31
+ return ptr ? wasmModule.UTF8ToString(ptr) : "empty";
32
+ }
33
+
34
+ function getSlotConfig(slot) {
35
+ return SLOT_CONFIG.find(cfg => cfg.slot === slot);
36
+ }
37
+
38
+ function setSlotCardWasm(wasmModule, slot, cardId) {
39
+ const cardIdPtr = wasmModule._malloc(cardId.length + 1);
40
+ wasmModule.stringToUTF8(cardId, cardIdPtr, cardId.length + 1);
41
+ wasmModule._setSlotCard(slot, cardIdPtr);
42
+ wasmModule._free(cardIdPtr);
43
+ }
44
+
45
+ function persistSlotConfig(wasmModule) {
46
+ try {
47
+ const config = {};
48
+ for (const cfg of SLOT_CONFIG) {
49
+ if (!cfg.fixed) {
50
+ config[cfg.slot] = getSlotCard(wasmModule, cfg.slot);
51
+ }
52
+ }
53
+ localStorage.setItem("a2e-slot-config", JSON.stringify(config));
54
+ } catch (e) {
55
+ // Non-fatal
56
+ }
57
+
58
+ // Refresh the slot configuration window UI if it exists
59
+ const slotWindow = window.emulator?.windowManager?.getWindow("slot-configuration");
60
+ if (slotWindow) {
61
+ slotWindow.loadSettings();
62
+ slotWindow.updateDisabledOptions();
63
+ }
64
+ }
65
+
66
+ export const slotTools = {
67
+ /**
68
+ * List all expansion slots with current card and available options
69
+ */
70
+ slotsListAll: async () => {
71
+ const wasmModule = getWasm();
72
+
73
+ // Get current card in every slot
74
+ const slotState = SLOT_CONFIG.map(cfg => ({
75
+ slot: cfg.slot,
76
+ currentCard: cfg.fixed ? "80col" : getSlotCard(wasmModule, cfg.slot),
77
+ fixed: cfg.fixed,
78
+ compatible: cfg.compatible,
79
+ }));
80
+
81
+ // Collect all cards currently installed
82
+ const installedCards = {};
83
+ for (const s of slotState) {
84
+ if (!s.fixed && s.currentCard !== "empty") {
85
+ installedCards[s.currentCard] = s.slot;
86
+ }
87
+ }
88
+
89
+ // For each slot, determine which compatible cards are available to install
90
+ const slots = slotState.map(s => {
91
+ const available = s.fixed
92
+ ? []
93
+ : s.compatible.filter(card => {
94
+ return !installedCards[card] || installedCards[card] === s.slot;
95
+ });
96
+
97
+ return {
98
+ slot: s.slot,
99
+ currentCard: s.currentCard,
100
+ fixed: s.fixed,
101
+ compatible: s.compatible,
102
+ available,
103
+ };
104
+ });
105
+
106
+ const uninstalled = ALL_CARDS.filter(card => !installedCards[card]);
107
+
108
+ return {
109
+ success: true,
110
+ slots,
111
+ installedCards,
112
+ uninstalledCards: uninstalled,
113
+ message: `${Object.keys(installedCards).length} card(s) installed, ${uninstalled.length} available`,
114
+ };
115
+ },
116
+
117
+ /**
118
+ * Install a card into a slot (auto-removes from previous slot if needed)
119
+ */
120
+ slotsInstallCard: async (args) => {
121
+ const { slot, card } = args;
122
+
123
+ if (slot === undefined || slot === null) {
124
+ throw new Error("slot parameter is required");
125
+ }
126
+ if (!card) {
127
+ throw new Error("card parameter is required");
128
+ }
129
+ if (!ALL_CARDS.includes(card)) {
130
+ throw new Error(`Unknown card: ${card}. Valid cards: ${ALL_CARDS.join(", ")}`);
131
+ }
132
+
133
+ const cfg = getSlotConfig(slot);
134
+ if (!cfg) {
135
+ throw new Error(`Invalid slot: ${slot}. Valid slots: 1-7`);
136
+ }
137
+ if (cfg.fixed) {
138
+ throw new Error(`Slot ${slot} is fixed and cannot be changed`);
139
+ }
140
+ if (!cfg.compatible.includes(card)) {
141
+ throw new Error(
142
+ `${card} is not compatible with slot ${slot}. Compatible: ${cfg.compatible.join(", ") || "none"}`
143
+ );
144
+ }
145
+
146
+ const wasmModule = getWasm();
147
+ const currentCard = getSlotCard(wasmModule, slot);
148
+
149
+ // Already installed in this slot
150
+ if (currentCard === card) {
151
+ return {
152
+ success: true,
153
+ slot,
154
+ card,
155
+ message: `${card} is already installed in slot ${slot}`,
156
+ reset: false,
157
+ };
158
+ }
159
+
160
+ // If the card is installed in another slot, remove it from there
161
+ let movedFrom = null;
162
+ for (const c of SLOT_CONFIG) {
163
+ if (!c.fixed && c.slot !== slot && getSlotCard(wasmModule, c.slot) === card) {
164
+ setSlotCardWasm(wasmModule, c.slot, "empty");
165
+ movedFrom = c.slot;
166
+ break;
167
+ }
168
+ }
169
+
170
+ // Install the card
171
+ setSlotCardWasm(wasmModule, slot, card);
172
+ persistSlotConfig(wasmModule);
173
+ wasmModule._reset();
174
+
175
+ const displaced = currentCard !== "empty" ? currentCard : null;
176
+ let message = `${card} installed in slot ${slot}. Emulator reset.`;
177
+ if (movedFrom) {
178
+ message = `${card} moved from slot ${movedFrom} to slot ${slot}. Emulator reset.`;
179
+ }
180
+ if (displaced && displaced !== card) {
181
+ message += ` Displaced ${displaced}.`;
182
+ }
183
+
184
+ return {
185
+ success: true,
186
+ slot,
187
+ card,
188
+ displaced,
189
+ movedFrom,
190
+ message,
191
+ reset: true,
192
+ };
193
+ },
194
+
195
+ /**
196
+ * Remove a card from a slot (set to empty)
197
+ */
198
+ slotsRemoveCard: async (args) => {
199
+ const { slot } = args;
200
+
201
+ if (slot === undefined || slot === null) {
202
+ throw new Error("slot parameter is required");
203
+ }
204
+
205
+ const cfg = getSlotConfig(slot);
206
+ if (!cfg) {
207
+ throw new Error(`Invalid slot: ${slot}. Valid slots: 1-7`);
208
+ }
209
+ if (cfg.fixed) {
210
+ throw new Error(`Slot ${slot} is fixed and cannot be changed`);
211
+ }
212
+
213
+ const wasmModule = getWasm();
214
+ const currentCard = getSlotCard(wasmModule, slot);
215
+
216
+ if (currentCard === "empty") {
217
+ return {
218
+ success: true,
219
+ slot,
220
+ message: `Slot ${slot} is already empty`,
221
+ reset: false,
222
+ };
223
+ }
224
+
225
+ setSlotCardWasm(wasmModule, slot, "empty");
226
+ persistSlotConfig(wasmModule);
227
+ wasmModule._reset();
228
+
229
+ return {
230
+ success: true,
231
+ slot,
232
+ removed: currentCard,
233
+ message: `Removed ${currentCard} from slot ${slot}. Emulator reset.`,
234
+ reset: true,
235
+ };
236
+ },
237
+
238
+ /**
239
+ * Move a card from one slot to another
240
+ */
241
+ slotsMoveCard: async (args) => {
242
+ const { fromSlot, toSlot } = args;
243
+
244
+ if (fromSlot === undefined || fromSlot === null) {
245
+ throw new Error("fromSlot parameter is required");
246
+ }
247
+ if (toSlot === undefined || toSlot === null) {
248
+ throw new Error("toSlot parameter is required");
249
+ }
250
+ if (fromSlot === toSlot) {
251
+ throw new Error("fromSlot and toSlot must be different");
252
+ }
253
+
254
+ const fromCfg = getSlotConfig(fromSlot);
255
+ const toCfg = getSlotConfig(toSlot);
256
+ if (!fromCfg) {
257
+ throw new Error(`Invalid fromSlot: ${fromSlot}. Valid slots: 1-7`);
258
+ }
259
+ if (!toCfg) {
260
+ throw new Error(`Invalid toSlot: ${toSlot}. Valid slots: 1-7`);
261
+ }
262
+ if (fromCfg.fixed) {
263
+ throw new Error(`Slot ${fromSlot} is fixed and cannot be changed`);
264
+ }
265
+ if (toCfg.fixed) {
266
+ throw new Error(`Slot ${toSlot} is fixed and cannot be changed`);
267
+ }
268
+
269
+ const wasmModule = getWasm();
270
+ const card = getSlotCard(wasmModule, fromSlot);
271
+
272
+ if (card === "empty") {
273
+ throw new Error(`Slot ${fromSlot} is empty — nothing to move`);
274
+ }
275
+
276
+ if (!toCfg.compatible.includes(card)) {
277
+ throw new Error(
278
+ `${card} is not compatible with slot ${toSlot}. Compatible: ${toCfg.compatible.join(", ") || "none"}`
279
+ );
280
+ }
281
+
282
+ const occupant = getSlotCard(wasmModule, toSlot);
283
+ if (occupant !== "empty") {
284
+ throw new Error(
285
+ `Slot ${toSlot} is occupied by ${occupant}. Remove it first with slotsRemoveCard.`
286
+ );
287
+ }
288
+
289
+ setSlotCardWasm(wasmModule, fromSlot, "empty");
290
+ setSlotCardWasm(wasmModule, toSlot, card);
291
+ persistSlotConfig(wasmModule);
292
+ wasmModule._reset();
293
+
294
+ return {
295
+ success: true,
296
+ card,
297
+ fromSlot,
298
+ toSlot,
299
+ message: `Moved ${card} from slot ${fromSlot} to slot ${toSlot}. Emulator reset.`,
300
+ reset: true,
301
+ };
302
+ },
303
+ };