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,894 @@
1
+ /*
2
+ * basic-program-tools.js - BASIC program window tools
3
+ *
4
+ * Written by
5
+ * Shawn Bullock <shawn@agenticexpert.ai>
6
+ */
7
+
8
+ import { BasicProgramParser } from "../debug/basic-program-parser.js";
9
+ import { tokenizeProgram } from "../utils/basic-tokenizer.js";
10
+
11
+ export const basicProgramTools = {
12
+ /**
13
+ * Direct read of BASIC program from memory (no UI interaction required)
14
+ */
15
+ directReadBasic: async (args) => {
16
+ const wasmModule = window.emulator?.wasmModule;
17
+ if (!wasmModule) {
18
+ throw new Error("WASM module not available");
19
+ }
20
+
21
+ // Create parser instance to read from memory
22
+ const parser = new BasicProgramParser(wasmModule);
23
+ const lines = parser.getLines();
24
+
25
+ if (lines.length === 0) {
26
+ return {
27
+ success: true,
28
+ program: "",
29
+ lines: 0,
30
+ message: "No BASIC program in memory",
31
+ };
32
+ }
33
+
34
+ // Format as program text (line number + text)
35
+ const programText = lines
36
+ .map((line) => `${line.lineNumber} ${line.text}`)
37
+ .join("\n");
38
+
39
+ return {
40
+ success: true,
41
+ program: programText,
42
+ lines: lines.length,
43
+ message: `Read ${lines.length} lines from memory`,
44
+ };
45
+ },
46
+
47
+ /**
48
+ * Direct write of BASIC program to memory (no UI interaction required)
49
+ */
50
+ directWriteBasic: async (args) => {
51
+ const { program } = args;
52
+
53
+ if (program === undefined) {
54
+ throw new Error("program parameter is required");
55
+ }
56
+
57
+ const wasmModule = window.emulator?.wasmModule;
58
+ if (!wasmModule) {
59
+ throw new Error("WASM module not available");
60
+ }
61
+
62
+ const emulator = window.emulator;
63
+ if (!emulator || !emulator.running) {
64
+ throw new Error("Emulator must be powered on");
65
+ }
66
+
67
+ // Parse program text into lines (matches window's parseProgram method)
68
+ let text = program.trim();
69
+ if (!text) {
70
+ throw new Error("No program text provided");
71
+ }
72
+
73
+ // Sanitize to pure ASCII: convert to ASCII and strip non-ASCII characters
74
+ // Replace any Unicode spaces with regular ASCII space (0x20)
75
+ text = text
76
+ .replace(/[\u00A0\u1680\u2000-\u200B\u202F\u205F\u3000]/g, ' ') // Unicode spaces → ASCII space
77
+ .replace(/[^\x00-\x7F]/g, ''); // Remove any non-ASCII characters
78
+
79
+ // Debug: Log the character codes to detect encoding issues
80
+ console.log("[directWriteBasic] Input text:", text);
81
+ console.log("[directWriteBasic] First 50 char codes:",
82
+ text.substring(0, 50).split('').map((c, i) =>
83
+ `[${i}]=${c}:0x${c.charCodeAt(0).toString(16)}`
84
+ ).join(' ')
85
+ );
86
+
87
+ const lines = [];
88
+ const rawLines = text.split(/\r?\n/);
89
+
90
+ for (const rawLine of rawLines) {
91
+ const trimmed = rawLine.trim().toUpperCase();
92
+ if (!trimmed) continue;
93
+
94
+ const match = trimmed.match(/^(\d+)\s*(.*)/);
95
+ if (!match) {
96
+ console.warn("Skipping line without line number:", rawLine);
97
+ continue;
98
+ }
99
+
100
+ const lineNum = parseInt(match[1], 10);
101
+ if (lineNum < 0 || lineNum > 63999) {
102
+ console.warn("Invalid line number:", lineNum);
103
+ continue;
104
+ }
105
+
106
+ // Normalize whitespace: replace multiple spaces with single space
107
+ // This matches ROM tokenizer behavior
108
+ const normalizedContent = (match[2] || "").replace(/\s+/g, " ");
109
+
110
+ lines.push({
111
+ lineNumber: lineNum,
112
+ content: normalizedContent,
113
+ });
114
+ }
115
+
116
+ // Sort lines by line number (critical for proper BASIC program order)
117
+ lines.sort((a, b) => a.lineNumber - b.lineNumber);
118
+
119
+ if (lines.length === 0) {
120
+ throw new Error("No valid BASIC lines found");
121
+ }
122
+
123
+ // Tokenize the program
124
+ const txttab = 0x0801;
125
+ const { bytes, endAddr } = tokenizeProgram(lines, txttab);
126
+
127
+ // Write tokenized program bytes into emulator memory
128
+ for (let i = 0; i < bytes.length; i++) {
129
+ wasmModule._writeMemory(txttab + i, bytes[i]);
130
+ }
131
+
132
+ // Helper to write a 16-bit little-endian pointer to zero page
133
+ const writePtr = (zpAddr, value) => {
134
+ wasmModule._writeMemory(zpAddr, value & 0xff);
135
+ wasmModule._writeMemory(zpAddr + 1, (value >> 8) & 0xff);
136
+ };
137
+
138
+ // Read MEMSIZE ($73) - the ROM sets FRETOP to this on CLR/NEW
139
+ const memsizeLo = wasmModule._readMemory(0x73);
140
+ const memsizeHi = wasmModule._readMemory(0x74);
141
+ const memsize = memsizeLo | (memsizeHi << 8);
142
+
143
+ // Update Applesoft zero page pointers
144
+ writePtr(0x67, txttab); // TXTTAB - start of program
145
+ writePtr(0x69, endAddr); // VARTAB - start of variable space
146
+ writePtr(0x6b, endAddr); // ARYTAB - start of array space
147
+ writePtr(0x6d, endAddr); // STREND - end of numeric storage
148
+ writePtr(0x6f, memsize); // FRETOP - end of string storage
149
+ writePtr(0xaf, endAddr); // PRGEND - end of program
150
+ writePtr(0xb8, txttab - 1); // TXTPTR - interpreter text pointer
151
+ wasmModule._writeMemory(0x76, 0xff); // CURLIN+1 = $FF (direct mode)
152
+
153
+ return {
154
+ success: true,
155
+ lines: lines.length,
156
+ bytes: bytes.length,
157
+ message: `Wrote ${lines.length} lines (${bytes.length} bytes) to memory`,
158
+ };
159
+ },
160
+
161
+ /**
162
+ * Direct run of BASIC program (no UI interaction required)
163
+ */
164
+ directRunBasic: async (args) => {
165
+ const wasmModule = window.emulator?.wasmModule;
166
+ if (!wasmModule) {
167
+ throw new Error("WASM module not available");
168
+ }
169
+
170
+ const emulator = window.emulator;
171
+ if (!emulator || !emulator.running) {
172
+ throw new Error("Emulator must be powered on");
173
+ }
174
+
175
+ const inputHandler = window.emulator?.inputHandler;
176
+ if (!inputHandler) {
177
+ throw new Error("Input handler not available");
178
+ }
179
+
180
+ // Ensure emulator is not paused
181
+ wasmModule._setPaused(false);
182
+
183
+ // Clear BASIC breakpoint hit flag if available
184
+ if (wasmModule._clearBasicBreakpointHit) {
185
+ wasmModule._clearBasicBreakpointHit();
186
+ }
187
+
188
+ // Queue RUN command to input handler
189
+ inputHandler.queueTextInput("RUN\r");
190
+
191
+ return {
192
+ success: true,
193
+ message: "BASIC program execution started",
194
+ };
195
+ },
196
+
197
+ /**
198
+ * Direct NEW command - clears BASIC program buffer (no UI interaction required)
199
+ */
200
+ directNewBasic: async (args) => {
201
+ const wasmModule = window.emulator?.wasmModule;
202
+ if (!wasmModule) {
203
+ throw new Error("WASM module not available");
204
+ }
205
+
206
+ const emulator = window.emulator;
207
+ if (!emulator || !emulator.running) {
208
+ throw new Error("Emulator must be powered on");
209
+ }
210
+
211
+ // Helper to write a 16-bit little-endian pointer to zero page
212
+ const writePtr = (zpAddr, value) => {
213
+ wasmModule._writeMemory(zpAddr, value & 0xff);
214
+ wasmModule._writeMemory(zpAddr + 1, (value >> 8) & 0xff);
215
+ };
216
+
217
+ // Read MEMSIZE ($73)
218
+ const memsizeLo = wasmModule._readMemory(0x73);
219
+ const memsizeHi = wasmModule._readMemory(0x74);
220
+ const memsize = memsizeLo | (memsizeHi << 8);
221
+
222
+ // TXTTAB - start of BASIC program area
223
+ const txttab = 0x0801;
224
+
225
+ // Write end-of-program marker (0x00, 0x00) at start
226
+ wasmModule._writeMemory(txttab, 0x00);
227
+ wasmModule._writeMemory(txttab + 1, 0x00);
228
+
229
+ // Reset all BASIC pointers to empty program state
230
+ writePtr(0x67, txttab); // TXTTAB - start of program
231
+ writePtr(0x69, txttab); // VARTAB - start of variables (same as program start = empty)
232
+ writePtr(0x6b, txttab); // ARYTAB - start of arrays
233
+ writePtr(0x6d, txttab); // STREND - end of arrays
234
+ writePtr(0x6f, memsize); // FRETOP - top of free memory
235
+ writePtr(0xaf, txttab); // PRGEND - end of program
236
+ writePtr(0xb8, txttab - 1); // TXTPTR - interpreter text pointer
237
+ wasmModule._writeMemory(0x76, 0xff); // CURLIN+1 = $FF (direct mode)
238
+
239
+ return {
240
+ success: true,
241
+ message: "BASIC program buffer cleared",
242
+ };
243
+ },
244
+
245
+ /**
246
+ * Get BASIC program content from editor (used with save_basic_file MCP tool)
247
+ */
248
+ saveBasicInEditorToLocal: async (args) => {
249
+ const windowManager = window.emulator?.windowManager;
250
+ if (!windowManager) {
251
+ throw new Error("Window manager not available");
252
+ }
253
+
254
+ const basicWindow = windowManager.getWindow("basic-program");
255
+ if (!basicWindow) {
256
+ throw new Error("BASIC program window not found");
257
+ }
258
+
259
+ const content = basicWindow.textarea ? basicWindow.textarea.value : "";
260
+ if (!content.trim()) {
261
+ throw new Error("No program in editor to save");
262
+ }
263
+
264
+ return {
265
+ success: true,
266
+ content: content,
267
+ lines: content.split("\n").length,
268
+ message: "BASIC program content retrieved from editor",
269
+ };
270
+ },
271
+
272
+ /**
273
+ * Save BASIC program from emulator memory to local file
274
+ * Combines directReadBasic + save to filesystem
275
+ */
276
+ directSaveBasicInMemoryToLocal: async (args) => {
277
+ const { path } = args;
278
+
279
+ if (!path) {
280
+ throw new Error("path parameter is required");
281
+ }
282
+
283
+ const wasmModule = window.emulator?.wasmModule;
284
+ if (!wasmModule) {
285
+ throw new Error("WASM module not available");
286
+ }
287
+
288
+ // Read BASIC program from memory
289
+ const parser = new BasicProgramParser(wasmModule);
290
+ const lines = parser.getLines();
291
+
292
+ if (lines.length === 0) {
293
+ throw new Error("No BASIC program in memory to save");
294
+ }
295
+
296
+ // Format as program text (line number + text)
297
+ const programText = lines
298
+ .map((line) => `${line.lineNumber} ${line.text}`)
299
+ .join("\n");
300
+
301
+ return {
302
+ success: true,
303
+ content: programText,
304
+ lines: lines.length,
305
+ path: path,
306
+ message: `BASIC program read from memory (${lines.length} lines), ready to save to ${path}`,
307
+ };
308
+ },
309
+
310
+ /**
311
+ * Load BASIC program from emulator memory into editor
312
+ */
313
+ basicProgramLoadFromMemory: async (args) => {
314
+ const windowManager = window.emulator?.windowManager;
315
+ if (!windowManager) {
316
+ throw new Error("Window manager not available");
317
+ }
318
+
319
+ const basicWindow = windowManager.getWindow("basic-program");
320
+ if (!basicWindow) {
321
+ throw new Error("BASIC program window not found");
322
+ }
323
+
324
+ basicWindow.loadFromMemory();
325
+
326
+ return {
327
+ success: true,
328
+ message: "BASIC program loaded from memory into editor",
329
+ };
330
+ },
331
+
332
+ /**
333
+ * Load BASIC program from editor into emulator memory
334
+ */
335
+ basicProgramLoadIntoEmulator: async (args) => {
336
+ const windowManager = window.emulator?.windowManager;
337
+ if (!windowManager) {
338
+ throw new Error("Window manager not available");
339
+ }
340
+
341
+ const basicWindow = windowManager.getWindow("basic-program");
342
+ if (!basicWindow) {
343
+ throw new Error("BASIC program window not found");
344
+ }
345
+
346
+ basicWindow.loadIntoMemory();
347
+
348
+ return {
349
+ success: true,
350
+ message: "BASIC program loaded from editor into emulator memory",
351
+ };
352
+ },
353
+
354
+ /**
355
+ * Run BASIC program
356
+ */
357
+ basicProgramRun: async (args) => {
358
+ const windowManager = window.emulator?.windowManager;
359
+ if (!windowManager) {
360
+ throw new Error("Window manager not available");
361
+ }
362
+
363
+ const basicWindow = windowManager.getWindow("basic-program");
364
+ if (!basicWindow) {
365
+ throw new Error("BASIC program window not found");
366
+ }
367
+
368
+ basicWindow.handleRun();
369
+
370
+ return {
371
+ success: true,
372
+ message: "BASIC program execution started",
373
+ };
374
+ },
375
+
376
+ /**
377
+ * Pause BASIC program execution
378
+ */
379
+ basicProgramPause: async (args) => {
380
+ const windowManager = window.emulator?.windowManager;
381
+ if (!windowManager) {
382
+ throw new Error("Window manager not available");
383
+ }
384
+
385
+ const basicWindow = windowManager.getWindow("basic-program");
386
+ if (!basicWindow) {
387
+ throw new Error("BASIC program window not found");
388
+ }
389
+
390
+ basicWindow.handlePause();
391
+
392
+ return {
393
+ success: true,
394
+ message: "BASIC program execution paused",
395
+ };
396
+ },
397
+
398
+ /**
399
+ * Clear BASIC program editor and start new program
400
+ * Emulates the newFile() method without confirmation dialog
401
+ */
402
+ basicProgramNew: async (args) => {
403
+ const windowManager = window.emulator?.windowManager;
404
+ if (!windowManager) {
405
+ throw new Error("Window manager not available");
406
+ }
407
+
408
+ const basicWindow = windowManager.getWindow("basic-program");
409
+ if (!basicWindow) {
410
+ throw new Error("BASIC program window not found");
411
+ }
412
+
413
+ // Emulate newFile() behavior without confirmation
414
+ if (basicWindow.textarea) {
415
+ basicWindow.textarea.value = "";
416
+ basicWindow._fileHandle = null;
417
+ basicWindow.updateGutter();
418
+ basicWindow.updateHighlighting();
419
+ basicWindow.updateStats();
420
+ }
421
+
422
+ return {
423
+ success: true,
424
+ message: "BASIC program editor cleared",
425
+ };
426
+ },
427
+
428
+ /**
429
+ * Renumber BASIC program lines
430
+ */
431
+ basicProgramRenumber: async (args) => {
432
+ const windowManager = window.emulator?.windowManager;
433
+ if (!windowManager) {
434
+ throw new Error("Window manager not available");
435
+ }
436
+
437
+ const basicWindow = windowManager.getWindow("basic-program");
438
+ if (!basicWindow) {
439
+ throw new Error("BASIC program window not found");
440
+ }
441
+
442
+ basicWindow.renumberProgram();
443
+
444
+ return {
445
+ success: true,
446
+ message: "BASIC program renumbered",
447
+ };
448
+ },
449
+
450
+ /**
451
+ * Format BASIC program code
452
+ */
453
+ basicProgramFormat: async (args) => {
454
+ const windowManager = window.emulator?.windowManager;
455
+ if (!windowManager) {
456
+ throw new Error("Window manager not available");
457
+ }
458
+
459
+ const basicWindow = windowManager.getWindow("basic-program");
460
+ if (!basicWindow) {
461
+ throw new Error("BASIC program window not found");
462
+ }
463
+
464
+ basicWindow.autoFormatCode();
465
+
466
+ return {
467
+ success: true,
468
+ message: "BASIC program formatted",
469
+ };
470
+ },
471
+
472
+ /**
473
+ * Get BASIC program line and character count
474
+ */
475
+ basicProgramLineCount: async (args) => {
476
+ const windowManager = window.emulator?.windowManager;
477
+ if (!windowManager) {
478
+ throw new Error("Window manager not available");
479
+ }
480
+
481
+ const basicWindow = windowManager.getWindow("basic-program");
482
+ if (!basicWindow) {
483
+ throw new Error("BASIC program window not found");
484
+ }
485
+
486
+ const text = basicWindow.textarea ? basicWindow.textarea.value : "";
487
+ const lines = text ? text.split(/\r?\n/).filter((l) => l.trim()).length : 0;
488
+ const chars = text.length;
489
+
490
+ return {
491
+ success: true,
492
+ lines: lines,
493
+ chars: chars,
494
+ message: `${lines} lines, ${chars} characters`,
495
+ };
496
+ },
497
+
498
+ /**
499
+ * Get current BASIC program text
500
+ */
501
+ basicProgramGet: async (args) => {
502
+ const windowManager = window.emulator?.windowManager;
503
+ if (!windowManager) {
504
+ throw new Error("Window manager not available");
505
+ }
506
+
507
+ const basicWindow = windowManager.getWindow("basic-program");
508
+ if (!basicWindow) {
509
+ throw new Error("BASIC program window not found");
510
+ }
511
+
512
+ const text = basicWindow.textarea ? basicWindow.textarea.value : "";
513
+
514
+ return {
515
+ success: true,
516
+ program: text,
517
+ message: "BASIC program retrieved",
518
+ };
519
+ },
520
+
521
+ /**
522
+ * Set BASIC program (replace entire content)
523
+ */
524
+ basicProgramSet: async (args) => {
525
+ const { program } = args;
526
+
527
+ if (program === undefined) {
528
+ throw new Error("program parameter is required");
529
+ }
530
+
531
+ const windowManager = window.emulator?.windowManager;
532
+ if (!windowManager) {
533
+ throw new Error("Window manager not available");
534
+ }
535
+
536
+ const basicWindow = windowManager.getWindow("basic-program");
537
+ if (!basicWindow) {
538
+ throw new Error("BASIC program window not found");
539
+ }
540
+
541
+ if (basicWindow.textarea) {
542
+ basicWindow.textarea.value = program;
543
+ basicWindow.updateGutter();
544
+ basicWindow.updateHighlighting();
545
+ basicWindow.updateStats();
546
+ }
547
+
548
+ return {
549
+ success: true,
550
+ message: "BASIC program set",
551
+ };
552
+ },
553
+
554
+ /**
555
+ * List all BASIC breakpoints
556
+ */
557
+ basicProgramListBreakpoints: async (args) => {
558
+ const windowManager = window.emulator?.windowManager;
559
+ if (!windowManager) {
560
+ throw new Error("Window manager not available");
561
+ }
562
+
563
+ const basicWindow = windowManager.getWindow("basic-program");
564
+ if (!basicWindow) {
565
+ throw new Error("BASIC program window not found");
566
+ }
567
+
568
+ const breakpointManager = basicWindow.getBreakpointManager();
569
+ if (!breakpointManager) {
570
+ throw new Error("Breakpoint manager not available");
571
+ }
572
+
573
+ const entries = breakpointManager.getAllEntries();
574
+ const breakpoints = entries.map((entry) => ({
575
+ lineNumber: entry.lineNumber,
576
+ statementIndex: entry.statementIndex,
577
+ enabled: entry.enabled,
578
+ type: entry.statementIndex === -1 ? "line" : "statement",
579
+ }));
580
+
581
+ return {
582
+ success: true,
583
+ breakpoints: breakpoints,
584
+ count: breakpoints.length,
585
+ message: `${breakpoints.length} breakpoint(s) set`,
586
+ };
587
+ },
588
+
589
+ /**
590
+ * Set a BASIC breakpoint on a line or statement
591
+ * @param {number} lineNumber - BASIC line number
592
+ * @param {number} statementIndex - Optional: -1 for whole line (default), 0+ for specific statement
593
+ */
594
+ basicProgramSetBreakpoint: async (args) => {
595
+ const { lineNumber, statementIndex = -1 } = args;
596
+
597
+ if (lineNumber === undefined) {
598
+ throw new Error("lineNumber parameter is required");
599
+ }
600
+
601
+ const windowManager = window.emulator?.windowManager;
602
+ if (!windowManager) {
603
+ throw new Error("Window manager not available");
604
+ }
605
+
606
+ const basicWindow = windowManager.getWindow("basic-program");
607
+ if (!basicWindow) {
608
+ throw new Error("BASIC program window not found");
609
+ }
610
+
611
+ const breakpointManager = basicWindow.getBreakpointManager();
612
+ if (!breakpointManager) {
613
+ throw new Error("Breakpoint manager not available");
614
+ }
615
+
616
+ breakpointManager.add(lineNumber, statementIndex);
617
+
618
+ const type = statementIndex === -1 ? "line" : `statement ${statementIndex}`;
619
+ return {
620
+ success: true,
621
+ lineNumber: lineNumber,
622
+ statementIndex: statementIndex,
623
+ message: `Breakpoint set on line ${lineNumber} (${type})`,
624
+ };
625
+ },
626
+
627
+ /**
628
+ * Remove a BASIC breakpoint from a line or statement
629
+ * @param {number} lineNumber - BASIC line number
630
+ * @param {number} statementIndex - Optional: -1 for whole line (default), 0+ for specific statement
631
+ */
632
+ basicProgramUnsetBreakpoint: async (args) => {
633
+ const { lineNumber, statementIndex = -1 } = args;
634
+
635
+ if (lineNumber === undefined) {
636
+ throw new Error("lineNumber parameter is required");
637
+ }
638
+
639
+ const windowManager = window.emulator?.windowManager;
640
+ if (!windowManager) {
641
+ throw new Error("Window manager not available");
642
+ }
643
+
644
+ const basicWindow = windowManager.getWindow("basic-program");
645
+ if (!basicWindow) {
646
+ throw new Error("BASIC program window not found");
647
+ }
648
+
649
+ const breakpointManager = basicWindow.getBreakpointManager();
650
+ if (!breakpointManager) {
651
+ throw new Error("Breakpoint manager not available");
652
+ }
653
+
654
+ breakpointManager.remove(lineNumber, statementIndex);
655
+
656
+ const type = statementIndex === -1 ? "line" : `statement ${statementIndex}`;
657
+ return {
658
+ success: true,
659
+ lineNumber: lineNumber,
660
+ statementIndex: statementIndex,
661
+ message: `Breakpoint removed from line ${lineNumber} (${type})`,
662
+ };
663
+ },
664
+
665
+ /**
666
+ * Get current BASIC line number
667
+ * Returns undefined if not stopped at a breakpoint
668
+ */
669
+ basicProgramGetCurrentLine: async (args) => {
670
+ const wasmModule = window.emulator?.wasmModule;
671
+ if (!wasmModule) {
672
+ throw new Error("WASM module not available");
673
+ }
674
+
675
+ const emulator = window.emulator;
676
+ if (!emulator || !emulator.running) {
677
+ return {
678
+ success: true,
679
+ lineNumber: undefined,
680
+ message: "Emulator not running",
681
+ };
682
+ }
683
+
684
+ // Check if paused at a BASIC breakpoint
685
+ const isPaused = wasmModule._isPaused();
686
+ const isBasicBreakpointHit = wasmModule._isBasicBreakpointHit
687
+ ? wasmModule._isBasicBreakpointHit()
688
+ : false;
689
+
690
+ if (!isPaused || !isBasicBreakpointHit) {
691
+ return {
692
+ success: true,
693
+ lineNumber: undefined,
694
+ message: "Not stopped at a breakpoint",
695
+ };
696
+ }
697
+
698
+ // Read CURLIN from zero page $75-$76
699
+ const lo = wasmModule._readMemory(0x75);
700
+ const hi = wasmModule._readMemory(0x76);
701
+ const lineNumber = lo | (hi << 8);
702
+
703
+ return {
704
+ success: true,
705
+ lineNumber: lineNumber,
706
+ message: `Stopped at line ${lineNumber}`,
707
+ };
708
+ },
709
+
710
+ /**
711
+ * Get all BASIC variables (simple and arrays)
712
+ */
713
+ basicProgramGetVariables: async (args) => {
714
+ const windowManager = window.emulator?.windowManager;
715
+ if (!windowManager) {
716
+ throw new Error("Window manager not available");
717
+ }
718
+
719
+ const basicWindow = windowManager.getWindow("basic-program");
720
+ if (!basicWindow) {
721
+ throw new Error("BASIC program window not found");
722
+ }
723
+
724
+ const inspector = basicWindow.variableInspector;
725
+ if (!inspector) {
726
+ throw new Error("Variable inspector not available");
727
+ }
728
+
729
+ const simpleVars = inspector.getSimpleVariables();
730
+ const arrayVars = inspector.getArrayVariables();
731
+
732
+ // Format simple variables
733
+ const variables = simpleVars.map((v) => ({
734
+ name: v.name,
735
+ type: v.type,
736
+ value: v.value,
737
+ formattedValue: inspector.formatValue(v),
738
+ }));
739
+
740
+ // Format array variables
741
+ const arrays = arrayVars.map((arr) => ({
742
+ name: arr.name,
743
+ type: arr.type,
744
+ dimensions: arr.dimensions,
745
+ totalElements: arr.totalElements,
746
+ values: arr.values,
747
+ }));
748
+
749
+ return {
750
+ success: true,
751
+ variables: variables,
752
+ arrays: arrays,
753
+ totalVariables: variables.length,
754
+ totalArrays: arrays.length,
755
+ message: `${variables.length} variable(s), ${arrays.length} array(s)`,
756
+ };
757
+ },
758
+
759
+ /**
760
+ * Set a BASIC variable value
761
+ * @param {string} name - Variable name (e.g., "X", "A$", "COUNT%")
762
+ * @param {string|number} value - New value (converted to string for parsing)
763
+ */
764
+ basicProgramSetVariable: async (args) => {
765
+ const { name, value } = args;
766
+
767
+ if (name === undefined) {
768
+ throw new Error("name parameter is required");
769
+ }
770
+
771
+ if (value === undefined) {
772
+ throw new Error("value parameter is required");
773
+ }
774
+
775
+ const windowManager = window.emulator?.windowManager;
776
+ if (!windowManager) {
777
+ throw new Error("Window manager not available");
778
+ }
779
+
780
+ const basicWindow = windowManager.getWindow("basic-program");
781
+ if (!basicWindow) {
782
+ throw new Error("BASIC program window not found");
783
+ }
784
+
785
+ const inspector = basicWindow.variableInspector;
786
+ if (!inspector) {
787
+ throw new Error("Variable inspector not available");
788
+ }
789
+
790
+ // Get all variables to find the one we want
791
+ const simpleVars = inspector.getSimpleVariables();
792
+ const varInfo = simpleVars.find((v) => v.name === name);
793
+
794
+ if (!varInfo) {
795
+ throw new Error(`Variable "${name}" not found`);
796
+ }
797
+
798
+ // Convert value to string for the inspector's setVariableValue method
799
+ const valueStr = typeof value === "string" ? value : String(value);
800
+
801
+ // Set the variable value
802
+ const success = inspector.setVariableValue(varInfo, valueStr);
803
+
804
+ if (!success) {
805
+ throw new Error(`Failed to set variable "${name}" to "${valueStr}"`);
806
+ }
807
+
808
+ // Refresh the UI to show the change immediately
809
+ if (basicWindow.renderVariables) {
810
+ basicWindow.renderVariables();
811
+ }
812
+
813
+ // Get the new value to confirm
814
+ const updatedVars = inspector.getSimpleVariables();
815
+ const updatedVar = updatedVars.find((v) => v.name === name);
816
+
817
+ return {
818
+ success: true,
819
+ name: name,
820
+ oldValue: varInfo.value,
821
+ newValue: updatedVar ? updatedVar.value : value,
822
+ type: varInfo.type,
823
+ message: `Variable ${name} set to ${inspector.formatValue(updatedVar || varInfo)}`,
824
+ };
825
+ },
826
+
827
+ /**
828
+ * Step to next BASIC line when paused at a breakpoint
829
+ */
830
+ basicProgramStepNext: async (args) => {
831
+ const wasmModule = window.emulator?.wasmModule;
832
+ if (!wasmModule) {
833
+ throw new Error("WASM module not available");
834
+ }
835
+
836
+ const emulator = window.emulator;
837
+ if (!emulator || !emulator.running) {
838
+ throw new Error("Emulator must be powered on");
839
+ }
840
+
841
+ // Check if emulator is paused
842
+ const isPaused = wasmModule._isPaused();
843
+ if (!isPaused) {
844
+ throw new Error("Emulator must be paused at a breakpoint to step");
845
+ }
846
+
847
+ // Check if we're at a BASIC breakpoint or in BASIC program
848
+ const isBasicBreakpointHit = wasmModule._isBasicBreakpointHit
849
+ ? wasmModule._isBasicBreakpointHit()
850
+ : false;
851
+ const isBasicRunning = wasmModule._isBasicProgramRunning
852
+ ? wasmModule._isBasicProgramRunning()
853
+ : false;
854
+
855
+ if (!isBasicBreakpointHit && !isBasicRunning) {
856
+ throw new Error("Not currently at a BASIC breakpoint");
857
+ }
858
+
859
+ // Helper to read CURLIN (current line number) from zero page $75-$76
860
+ const readCurlin = () => {
861
+ const lo = wasmModule._readMemory(0x75);
862
+ const hi = wasmModule._readMemory(0x76);
863
+ return lo | (hi << 8);
864
+ };
865
+
866
+ // Get current line before stepping (read CURLIN from zero page)
867
+ const previousLine = readCurlin();
868
+
869
+ // Clear breakpoint hit flag
870
+ if (wasmModule._clearBasicBreakpointHit) {
871
+ wasmModule._clearBasicBreakpointHit();
872
+ }
873
+
874
+ // Step to next BASIC line
875
+ if (wasmModule._stepBasicLine) {
876
+ wasmModule._stepBasicLine();
877
+ } else {
878
+ throw new Error("BASIC line stepping not supported");
879
+ }
880
+
881
+ // Wait a brief moment for step to complete, then read new line
882
+ await new Promise((resolve) => setTimeout(resolve, 50));
883
+
884
+ // Get new line after stepping (read CURLIN again)
885
+ const currentLine = readCurlin();
886
+
887
+ return {
888
+ success: true,
889
+ previousLine: previousLine,
890
+ currentLine: currentLine,
891
+ message: `Stepped from line ${previousLine} to line ${currentLine}`,
892
+ };
893
+ },
894
+ };