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,447 @@
1
+ /*
2
+ * basic-variable-inspector.js - Parse and display Applesoft BASIC variables
3
+ *
4
+ * Written by
5
+ * Mike Daley <michael_daley@icloud.com>
6
+ */
7
+
8
+ /**
9
+ * Applesoft BASIC Variable Memory Layout:
10
+ * - Simple variables: VARTAB ($69-$6A) to ARYTAB ($6B-$6C)
11
+ * - Arrays: ARYTAB ($6B-$6C) to STREND ($6D-$6E)
12
+ *
13
+ * Variable name format (2 bytes):
14
+ * - First byte: First char (A-Z), bit 7 = integer type if set on BOTH bytes
15
+ * - Second byte: Second char (0-9, A-Z, or null), high bit set for string type
16
+ *
17
+ * Value format:
18
+ * - Real (5 bytes): Applesoft floating point
19
+ * - Integer (2 bytes): Signed 16-bit (high byte, low byte)
20
+ * - String (3 bytes): Length byte + 2-byte pointer to string data
21
+ */
22
+
23
+ import { peek, readWord } from "../utils/wasm-memory.js";
24
+
25
+ export class BasicVariableInspector {
26
+ constructor(wasmModule) {
27
+ this.wasmModule = wasmModule;
28
+ }
29
+
30
+ /**
31
+ * Get all simple variables from memory
32
+ * @returns {Array<{name: string, type: string, value: any, rawValue: Uint8Array}>}
33
+ */
34
+ getSimpleVariables() {
35
+ const variables = [];
36
+
37
+ const vartab = readWord(this.wasmModule,0x69);
38
+ const arytab = readWord(this.wasmModule,0x6b);
39
+
40
+ // No variables if pointers are invalid or equal (empty variable area)
41
+ if (vartab === 0 || arytab === 0 || vartab >= arytab) {
42
+ return variables;
43
+ }
44
+
45
+ // Sanity check - variable area should be in reasonable range
46
+ if (vartab < 0x800 || arytab > 0xC000) {
47
+ return variables;
48
+ }
49
+
50
+ let addr = vartab;
51
+ while (addr < arytab) {
52
+ const varInfo = this._parseVariable(addr);
53
+ if (!varInfo) break;
54
+
55
+ variables.push(varInfo);
56
+ addr += varInfo.size;
57
+ }
58
+
59
+ return variables;
60
+ }
61
+
62
+ /**
63
+ * Get all array variables from memory
64
+ * @returns {Array<{name: string, type: string, dimensions: number[], values: any[]}>}
65
+ */
66
+ getArrayVariables() {
67
+ const arrays = [];
68
+
69
+ const arytab = readWord(this.wasmModule,0x6b);
70
+ const strend = readWord(this.wasmModule,0x6d);
71
+
72
+ if (arytab === 0 || strend === 0 || arytab >= strend) {
73
+ return arrays;
74
+ }
75
+
76
+ let addr = arytab;
77
+ while (addr < strend) {
78
+ const arrayInfo = this._parseArray(addr);
79
+ if (!arrayInfo) break;
80
+
81
+ arrays.push(arrayInfo);
82
+ addr += arrayInfo.totalSize;
83
+ }
84
+
85
+ return arrays;
86
+ }
87
+
88
+ /**
89
+ * Parse a single variable at the given address
90
+ */
91
+ _parseVariable(addr) {
92
+ const byte1 = peek(this.wasmModule,addr);
93
+ const byte2 = peek(this.wasmModule,addr + 1);
94
+
95
+ if (byte1 === 0) return null;
96
+
97
+ const { name, type } = this._parseVariableName(byte1, byte2);
98
+ let value;
99
+ let rawValue;
100
+ let size;
101
+
102
+ if (type === "integer") {
103
+ // Integer: 2 bytes (high, low)
104
+ const high = peek(this.wasmModule,addr + 2);
105
+ const low = peek(this.wasmModule,addr + 3);
106
+ value = (high << 8) | low;
107
+ // Convert to signed
108
+ if (value >= 0x8000) value -= 0x10000;
109
+ rawValue = new Uint8Array([high, low]);
110
+ size = 7; // 2 name + 5 value (padded to match real size)
111
+ } else if (type === "string") {
112
+ // String: length + 2-byte pointer
113
+ const len = peek(this.wasmModule,addr + 2);
114
+ const ptrLow = peek(this.wasmModule,addr + 3);
115
+ const ptrHigh = peek(this.wasmModule,addr + 4);
116
+ const ptr = (ptrHigh << 8) | ptrLow;
117
+ value = this._readString(ptr, len);
118
+ rawValue = new Uint8Array([len, ptrLow, ptrHigh]);
119
+ size = 7; // 2 name + 3 value + 2 padding
120
+ } else {
121
+ // Real: 5-byte Applesoft float
122
+ const floatBytes = new Uint8Array(5);
123
+ for (let i = 0; i < 5; i++) {
124
+ floatBytes[i] = peek(this.wasmModule,addr + 2 + i);
125
+ }
126
+ value = this._decodeApplesoftFloat(floatBytes);
127
+ rawValue = floatBytes;
128
+ size = 7; // 2 name + 5 value
129
+ }
130
+
131
+ return { name, type, value, rawValue, size, addr };
132
+ }
133
+
134
+ /**
135
+ * Parse an array variable header
136
+ */
137
+ _parseArray(addr) {
138
+ const byte1 = peek(this.wasmModule,addr);
139
+ const byte2 = peek(this.wasmModule,addr + 1);
140
+
141
+ if (byte1 === 0) return null;
142
+
143
+ const { name, type } = this._parseVariableName(byte1, byte2);
144
+
145
+ // Total size of array entry (including header) - stored little-endian
146
+ const sizeLow = peek(this.wasmModule,addr + 2);
147
+ const sizeHigh = peek(this.wasmModule,addr + 3);
148
+ const totalSize = (sizeHigh << 8) | sizeLow;
149
+
150
+ // Number of dimensions
151
+ const numDims = peek(this.wasmModule,addr + 4);
152
+
153
+ // Read dimension sizes (2 bytes each, stored high-low)
154
+ const dimensions = [];
155
+ let dimAddr = addr + 5;
156
+ for (let i = 0; i < numDims; i++) {
157
+ const dimHigh = peek(this.wasmModule,dimAddr);
158
+ const dimLow = peek(this.wasmModule,dimAddr + 1);
159
+ dimensions.push((dimHigh << 8) | dimLow);
160
+ dimAddr += 2;
161
+ }
162
+
163
+ // Calculate total elements
164
+ let totalElements = 1;
165
+ for (const dim of dimensions) {
166
+ totalElements *= dim;
167
+ }
168
+
169
+ // Read values
170
+ const values = [];
171
+ let valueAddr = dimAddr;
172
+ const elementSize = type === "integer" ? 2 : type === "string" ? 3 : 5;
173
+
174
+ // Read all elements (Applesoft arrays are limited by memory anyway)
175
+ for (let i = 0; i < totalElements && i < 10000; i++) {
176
+ let elemValue;
177
+ if (type === "integer") {
178
+ const high = peek(this.wasmModule,valueAddr);
179
+ const low = peek(this.wasmModule,valueAddr + 1);
180
+ elemValue = (high << 8) | low;
181
+ if (elemValue >= 0x8000) elemValue -= 0x10000;
182
+ } else if (type === "string") {
183
+ const len = peek(this.wasmModule,valueAddr);
184
+ const ptrLow = peek(this.wasmModule,valueAddr + 1);
185
+ const ptrHigh = peek(this.wasmModule,valueAddr + 2);
186
+ const ptr = (ptrHigh << 8) | ptrLow;
187
+ elemValue = this._readString(ptr, len);
188
+ } else {
189
+ const floatBytes = new Uint8Array(5);
190
+ for (let j = 0; j < 5; j++) {
191
+ floatBytes[j] = peek(this.wasmModule,valueAddr + j);
192
+ }
193
+ elemValue = this._decodeApplesoftFloat(floatBytes);
194
+ }
195
+ values.push(elemValue);
196
+ valueAddr += elementSize;
197
+ }
198
+
199
+ return {
200
+ name,
201
+ type,
202
+ dimensions,
203
+ values,
204
+ totalSize,
205
+ totalElements,
206
+ addr,
207
+ numDims,
208
+ };
209
+ }
210
+
211
+ /**
212
+ * Parse variable name from two bytes
213
+ */
214
+ _parseVariableName(byte1, byte2) {
215
+ // Extract characters (mask off high bits for char value)
216
+ const char1 = String.fromCharCode(byte1 & 0x7f);
217
+ const char2Raw = byte2 & 0x7f;
218
+ const char2 = char2Raw ? String.fromCharCode(char2Raw) : "";
219
+
220
+ // Determine type from high bits
221
+ // Integer: both high bits set
222
+ // String: second byte high bit set (but not both)
223
+ const isInteger = (byte1 & 0x80) !== 0 && (byte2 & 0x80) !== 0;
224
+ const isString = !isInteger && (byte2 & 0x80) !== 0;
225
+
226
+ let type = "real";
227
+ let suffix = "";
228
+ if (isInteger) {
229
+ type = "integer";
230
+ suffix = "%";
231
+ } else if (isString) {
232
+ type = "string";
233
+ suffix = "$";
234
+ }
235
+
236
+ return {
237
+ name: char1 + char2 + suffix,
238
+ type,
239
+ };
240
+ }
241
+
242
+ /**
243
+ * Decode Applesoft floating point format
244
+ * Format: exponent (1 byte) + mantissa (4 bytes)
245
+ * Exponent: excess-128 (0 = zero value)
246
+ * Mantissa: normalized with implied leading 1, sign in bit 7 of first mantissa byte
247
+ */
248
+ _decodeApplesoftFloat(bytes) {
249
+ const exp = bytes[0];
250
+
251
+ // Zero check
252
+ if (exp === 0) return 0;
253
+
254
+ // Sign is in bit 7 of mantissa byte 1
255
+ const sign = bytes[1] & 0x80 ? -1 : 1;
256
+
257
+ // Build mantissa as a number between 1 and 2 (normalized 1.xxxxx form)
258
+ // The implied leading 1 is not stored, so we start with 1.0
259
+ // Then add the fractional bits from the mantissa bytes
260
+ let mantissa = 1.0;
261
+ mantissa += (bytes[1] & 0x7F) / 128.0; // 7 bits: values 0.5, 0.25, 0.125, etc.
262
+ mantissa += bytes[2] / 32768.0; // next 8 bits
263
+ mantissa += bytes[3] / 8388608.0; // next 8 bits
264
+ mantissa += bytes[4] / 2147483648.0; // last 8 bits
265
+
266
+ // Applesoft uses excess-129 notation (the implied 1 is at position 2^-1, not 2^0)
267
+ // So the actual exponent is exp - 129
268
+ const actualExp = exp - 129;
269
+ const value = sign * mantissa * Math.pow(2, actualExp);
270
+
271
+ return value;
272
+ }
273
+
274
+ /**
275
+ * Read a string from memory
276
+ */
277
+ _readString(ptr, len) {
278
+ if (len === 0 || ptr === 0) return "";
279
+
280
+ let str = "";
281
+ for (let i = 0; i < len; i++) {
282
+ const char = peek(this.wasmModule,ptr + i) & 0x7f;
283
+ str += String.fromCharCode(char);
284
+ }
285
+ return str;
286
+ }
287
+
288
+ /**
289
+ * Write a new value to a simple variable in memory
290
+ * @param {Object} varInfo - Variable info from getSimpleVariables() (must include addr and type)
291
+ * @param {string} newValueStr - New value as a string entered by the user
292
+ * @returns {boolean} true if the write succeeded
293
+ */
294
+ setVariableValue(varInfo, newValueStr) {
295
+ const { addr, type } = varInfo;
296
+ const valueAddr = addr + 2; // skip 2-byte name
297
+
298
+ if (type === "integer") {
299
+ const parsed = parseInt(newValueStr, 10);
300
+ if (isNaN(parsed) || parsed < -32768 || parsed > 32767) return false;
301
+ const unsigned = parsed < 0 ? parsed + 0x10000 : parsed;
302
+ this.wasmModule._writeMemory(valueAddr, (unsigned >> 8) & 0xff);
303
+ this.wasmModule._writeMemory(valueAddr + 1, unsigned & 0xff);
304
+ return true;
305
+ } else if (type === "string") {
306
+ // String editing: write new characters into the existing string buffer
307
+ // We can only write up to the original allocated length
308
+ const origLen = peek(this.wasmModule, valueAddr);
309
+ const ptr = peek(this.wasmModule, valueAddr + 1) | (peek(this.wasmModule, valueAddr + 2) << 8);
310
+ // Strip surrounding quotes if present
311
+ let str = newValueStr;
312
+ if (str.startsWith('"') && str.endsWith('"')) str = str.slice(1, -1);
313
+ if (str.length > origLen) str = str.slice(0, origLen);
314
+ // Update length
315
+ this.wasmModule._writeMemory(valueAddr, str.length);
316
+ // Write characters
317
+ for (let i = 0; i < str.length; i++) {
318
+ this.wasmModule._writeMemory(ptr + i, str.charCodeAt(i) | 0x80);
319
+ }
320
+ return true;
321
+ } else {
322
+ // Real number
323
+ const parsed = parseFloat(newValueStr);
324
+ if (isNaN(parsed)) return false;
325
+ const bytes = this._encodeApplesoftFloat(parsed);
326
+ for (let i = 0; i < 5; i++) {
327
+ this.wasmModule._writeMemory(valueAddr + i, bytes[i]);
328
+ }
329
+ return true;
330
+ }
331
+ }
332
+
333
+ /**
334
+ * Write a new value to an array element in memory
335
+ * @param {Object} info - { addr, type, numDims, elementIndex }
336
+ * addr: start address of the array entry in memory
337
+ * type: 'real', 'integer', or 'string'
338
+ * numDims: number of dimensions
339
+ * elementIndex: flat index of the element
340
+ * @param {string} newValueStr - New value as a string
341
+ * @returns {boolean} true if the write succeeded
342
+ */
343
+ setArrayElementValue(info, newValueStr) {
344
+ const { addr, type, numDims, elementIndex } = info;
345
+ const elementSize = type === "integer" ? 2 : type === "string" ? 3 : 5;
346
+ const dataStart = addr + 5 + numDims * 2;
347
+ const elemAddr = dataStart + elementIndex * elementSize;
348
+
349
+ if (type === "integer") {
350
+ const parsed = parseInt(newValueStr, 10);
351
+ if (isNaN(parsed) || parsed < -32768 || parsed > 32767) return false;
352
+ const unsigned = parsed < 0 ? parsed + 0x10000 : parsed;
353
+ this.wasmModule._writeMemory(elemAddr, (unsigned >> 8) & 0xff);
354
+ this.wasmModule._writeMemory(elemAddr + 1, unsigned & 0xff);
355
+ return true;
356
+ } else if (type === "string") {
357
+ const origLen = peek(this.wasmModule, elemAddr);
358
+ const ptr = peek(this.wasmModule, elemAddr + 1) | (peek(this.wasmModule, elemAddr + 2) << 8);
359
+ let str = newValueStr;
360
+ if (str.startsWith('"') && str.endsWith('"')) str = str.slice(1, -1);
361
+ if (str.length > origLen) str = str.slice(0, origLen);
362
+ this.wasmModule._writeMemory(elemAddr, str.length);
363
+ for (let i = 0; i < str.length; i++) {
364
+ this.wasmModule._writeMemory(ptr + i, str.charCodeAt(i) | 0x80);
365
+ }
366
+ return true;
367
+ } else {
368
+ const parsed = parseFloat(newValueStr);
369
+ if (isNaN(parsed)) return false;
370
+ const bytes = this._encodeApplesoftFloat(parsed);
371
+ for (let i = 0; i < 5; i++) {
372
+ this.wasmModule._writeMemory(elemAddr + i, bytes[i]);
373
+ }
374
+ return true;
375
+ }
376
+ }
377
+
378
+ /**
379
+ * Encode a JavaScript number into 5-byte Applesoft floating point format
380
+ */
381
+ _encodeApplesoftFloat(value) {
382
+ const bytes = new Uint8Array(5);
383
+ if (value === 0) return bytes; // all zeros = 0.0
384
+
385
+ const sign = value < 0 ? 1 : 0;
386
+ let abs = Math.abs(value);
387
+
388
+ // Find exponent: normalize so 1.0 <= mantissa < 2.0
389
+ let exp = Math.floor(Math.log2(abs));
390
+ let mantissa = abs / Math.pow(2, exp);
391
+
392
+ // Adjust if mantissa is out of range due to floating point
393
+ if (mantissa < 1.0) { mantissa *= 2; exp--; }
394
+ if (mantissa >= 2.0) { mantissa /= 2; exp++; }
395
+
396
+ // Applesoft exponent is excess-129
397
+ const expByte = exp + 129;
398
+ if (expByte <= 0 || expByte > 255) return bytes; // underflow/overflow -> 0
399
+
400
+ bytes[0] = expByte;
401
+
402
+ // Remove the implicit leading 1
403
+ mantissa -= 1.0;
404
+
405
+ // Encode 31 bits of fractional mantissa across bytes[1..4]
406
+ // byte[1] has 7 fraction bits + sign in bit 7
407
+ mantissa *= 128; // 2^7
408
+ bytes[1] = (Math.floor(mantissa) & 0x7f) | (sign << 7);
409
+ mantissa -= Math.floor(mantissa);
410
+
411
+ mantissa *= 256;
412
+ bytes[2] = Math.floor(mantissa) & 0xff;
413
+ mantissa -= Math.floor(mantissa);
414
+
415
+ mantissa *= 256;
416
+ bytes[3] = Math.floor(mantissa) & 0xff;
417
+ mantissa -= Math.floor(mantissa);
418
+
419
+ mantissa *= 256;
420
+ bytes[4] = Math.round(mantissa) & 0xff;
421
+
422
+ return bytes;
423
+ }
424
+
425
+ /**
426
+ * Format a value for display
427
+ */
428
+ formatValue(variable) {
429
+ if (variable.type === "string") {
430
+ return `"${variable.value}"`;
431
+ } else if (variable.type === "integer") {
432
+ return variable.value.toString();
433
+ } else {
434
+ // Real number
435
+ if (Number.isInteger(variable.value)) {
436
+ return variable.value.toString();
437
+ }
438
+ // Format with reasonable precision
439
+ const absVal = Math.abs(variable.value);
440
+ if (absVal === 0) return "0";
441
+ if (absVal >= 0.01 && absVal < 1e7) {
442
+ return variable.value.toPrecision(9).replace(/\.?0+$/, "");
443
+ }
444
+ return variable.value.toExponential(6);
445
+ }
446
+ }
447
+ }