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,472 @@
1
+ /*
2
+ * breakpoint-manager.js - CPU breakpoint and watchpoint management
3
+ *
4
+ * Written by
5
+ * Mike Daley <michael_daley@icloud.com>
6
+ */
7
+
8
+ /**
9
+ * BreakpointManager - Manages all breakpoint types including
10
+ * execution breakpoints, conditional breakpoints, and watchpoints.
11
+ */
12
+ export class BreakpointManager {
13
+ constructor(wasmModule) {
14
+ this.wasmModule = wasmModule;
15
+ this.breakpoints = new Map(); // address -> BreakpointEntry
16
+ this.tempBreakpoint = null;
17
+ this.listeners = [];
18
+
19
+ this.load();
20
+ }
21
+
22
+ static STORAGE_KEY = "a2e-breakpoints-v2";
23
+
24
+ /**
25
+ * Add a change listener
26
+ */
27
+ onChange(fn) {
28
+ this.listeners.push(fn);
29
+ }
30
+
31
+ _notify() {
32
+ for (const fn of this.listeners) fn();
33
+ }
34
+
35
+ /**
36
+ * Add an execution breakpoint
37
+ */
38
+ add(address, opts = {}) {
39
+ if (this.breakpoints.has(address)) return;
40
+
41
+ const entry = {
42
+ address,
43
+ endAddress: opts.endAddress ?? address,
44
+ name: opts.name || null,
45
+ enabled: opts.enabled !== false,
46
+ condition: opts.condition || null,
47
+ conditionRules: opts.conditionRules || null,
48
+ hitCount: 0,
49
+ hitTarget: opts.hitTarget || 0,
50
+ isTemp: false,
51
+ type: opts.type || "exec", // 'exec' | 'read' | 'write' | 'readwrite'
52
+ };
53
+
54
+ this.breakpoints.set(address, entry);
55
+
56
+ if (entry.type === "exec") {
57
+ this._syncWasmAdd(address, entry.enabled);
58
+ } else {
59
+ this._syncWatchpointAdd(entry);
60
+ }
61
+
62
+ this.save();
63
+ this._notify();
64
+ }
65
+
66
+ /**
67
+ * Remove a breakpoint
68
+ */
69
+ remove(address) {
70
+ const entry = this.breakpoints.get(address);
71
+ if (!entry) return;
72
+
73
+ this.breakpoints.delete(address);
74
+
75
+ if (entry.type === "exec") {
76
+ this._syncWasmRemove(address);
77
+ } else {
78
+ this._syncWatchpointRemove(address);
79
+ }
80
+
81
+ this.save();
82
+ this._notify();
83
+ }
84
+
85
+ /**
86
+ * Toggle a breakpoint on/off at address
87
+ */
88
+ toggle(address) {
89
+ if (this.breakpoints.has(address)) {
90
+ this.remove(address);
91
+ } else {
92
+ this.add(address);
93
+ }
94
+ }
95
+
96
+ /**
97
+ * Enable/disable a breakpoint
98
+ */
99
+ setEnabled(address, enabled) {
100
+ const entry = this.breakpoints.get(address);
101
+ if (!entry) return;
102
+
103
+ entry.enabled = enabled;
104
+
105
+ if (entry.type === "exec") {
106
+ try {
107
+ this.wasmModule._enableBreakpoint(address, enabled);
108
+ } catch (e) {
109
+ /* ignore */
110
+ }
111
+ }
112
+
113
+ this.save();
114
+ this._notify();
115
+ }
116
+
117
+ /**
118
+ * Set condition on a breakpoint
119
+ */
120
+ setCondition(address, condition) {
121
+ const entry = this.breakpoints.get(address);
122
+ if (!entry) return;
123
+ entry.condition = condition || null;
124
+ this.save();
125
+ this._notify();
126
+ }
127
+
128
+ /**
129
+ * Set the structured rule tree on a breakpoint (for Rule Builder persistence)
130
+ */
131
+ setConditionRules(address, rules) {
132
+ const entry = this.breakpoints.get(address);
133
+ if (!entry) return;
134
+ entry.conditionRules = rules || null;
135
+ this.save();
136
+ this._notify();
137
+ }
138
+
139
+ /**
140
+ * Set hit target on a breakpoint
141
+ */
142
+ setHitTarget(address, target) {
143
+ const entry = this.breakpoints.get(address);
144
+ if (!entry) return;
145
+ entry.hitTarget = target;
146
+ this.save();
147
+ this._notify();
148
+ }
149
+
150
+ /**
151
+ * Reset hit counts on all breakpoints
152
+ */
153
+ resetHitCounts() {
154
+ for (const entry of this.breakpoints.values()) {
155
+ entry.hitCount = 0;
156
+ }
157
+ this._notify();
158
+ }
159
+
160
+ /**
161
+ * Get a breakpoint entry
162
+ */
163
+ get(address) {
164
+ return this.breakpoints.get(address) || null;
165
+ }
166
+
167
+ /**
168
+ * Check if an address has a breakpoint
169
+ */
170
+ has(address) {
171
+ return this.breakpoints.has(address);
172
+ }
173
+
174
+ /**
175
+ * Get all breakpoints
176
+ */
177
+ getAll() {
178
+ return this.breakpoints;
179
+ }
180
+
181
+ /**
182
+ * Get breakpoints of a specific type
183
+ */
184
+ getByType(type) {
185
+ const result = [];
186
+ for (const entry of this.breakpoints.values()) {
187
+ if (entry.type === type) result.push(entry);
188
+ }
189
+ return result;
190
+ }
191
+
192
+ /**
193
+ * Set a temporary breakpoint (for step over/out/run to cursor)
194
+ */
195
+ setTemp(address) {
196
+ this.clearTemp();
197
+ this.tempBreakpoint = address;
198
+ try {
199
+ this.wasmModule._addBreakpoint(address);
200
+ } catch (e) {
201
+ /* ignore */
202
+ }
203
+ }
204
+
205
+ /**
206
+ * Sync temp breakpoint state from C++ (step over/out sets it in C++)
207
+ */
208
+ syncTemp(address) {
209
+ this.tempBreakpoint = address;
210
+ }
211
+
212
+ /**
213
+ * Clear the temporary breakpoint
214
+ */
215
+ clearTemp() {
216
+ if (this.tempBreakpoint !== null) {
217
+ if (!this.breakpoints.has(this.tempBreakpoint)) {
218
+ try {
219
+ this.wasmModule._removeBreakpoint(this.tempBreakpoint);
220
+ } catch (e) {
221
+ /* ignore */
222
+ }
223
+ }
224
+ this.tempBreakpoint = null;
225
+ }
226
+ // Also clear C++ temp breakpoint
227
+ try {
228
+ this.wasmModule._clearTempBreakpoint();
229
+ } catch (e) {
230
+ /* ignore */
231
+ }
232
+ }
233
+
234
+ /**
235
+ * Check if temp breakpoint was hit (call in update loop)
236
+ */
237
+ checkTemp(pc) {
238
+ // Check C++ temp breakpoint first
239
+ try {
240
+ if (this.wasmModule._isTempBreakpointHit()) {
241
+ this.tempBreakpoint = null;
242
+ return true;
243
+ }
244
+ } catch (e) {
245
+ /* ignore */
246
+ }
247
+ if (this.tempBreakpoint !== null && pc === this.tempBreakpoint) {
248
+ this.clearTemp();
249
+ return true;
250
+ }
251
+ return false;
252
+ }
253
+
254
+ /**
255
+ * Evaluate whether a breakpoint should actually fire.
256
+ * Returns true if we should stay paused, false if we should resume.
257
+ */
258
+ shouldBreak(address) {
259
+ const entry = this.breakpoints.get(address);
260
+ if (!entry || !entry.enabled) return false;
261
+
262
+ // Increment hit count
263
+ entry.hitCount++;
264
+
265
+ // Check hit target
266
+ if (entry.hitTarget > 0 && entry.hitCount < entry.hitTarget) {
267
+ return false;
268
+ }
269
+
270
+ // Evaluate condition
271
+ if (entry.condition) {
272
+ try {
273
+ const result = this.evaluateCondition(entry.condition);
274
+ if (!result) return false;
275
+ } catch (e) {
276
+ // If condition evaluation fails, break anyway
277
+ console.warn("Breakpoint condition error:", e.message);
278
+ }
279
+ }
280
+
281
+ return true;
282
+ }
283
+
284
+ /**
285
+ * Find a breakpoint entry whose address range contains the given address.
286
+ * For non-exec types, checks if addr falls within [address, endAddress].
287
+ * Returns the matching entry or null.
288
+ */
289
+ findByAddress(addr) {
290
+ for (const entry of this.breakpoints.values()) {
291
+ if (entry.type === "exec") continue;
292
+ const end = entry.endAddress ?? entry.address;
293
+ if (addr >= entry.address && addr <= end) {
294
+ return entry;
295
+ }
296
+ }
297
+ return null;
298
+ }
299
+
300
+ /**
301
+ * Evaluate whether a given breakpoint entry should actually fire.
302
+ * Like shouldBreak() but accepts an entry directly (for range-based lookups).
303
+ */
304
+ shouldBreakEntry(entry) {
305
+ if (!entry || !entry.enabled) return false;
306
+
307
+ entry.hitCount++;
308
+
309
+ if (entry.hitTarget > 0 && entry.hitCount < entry.hitTarget) {
310
+ return false;
311
+ }
312
+
313
+ if (entry.condition) {
314
+ try {
315
+ const result = this.evaluateCondition(entry.condition);
316
+ if (!result) return false;
317
+ } catch (e) {
318
+ console.warn("Breakpoint condition error:", e.message);
319
+ }
320
+ }
321
+
322
+ return true;
323
+ }
324
+
325
+ /**
326
+ * Evaluate a breakpoint condition expression via C++ evaluator.
327
+ */
328
+ evaluateCondition(expr) {
329
+ const exprPtr = this.wasmModule._malloc(expr.length + 1);
330
+ this.wasmModule.stringToUTF8(expr, exprPtr, expr.length + 1);
331
+ const result = this.wasmModule._evaluateCondition(exprPtr);
332
+ this.wasmModule._free(exprPtr);
333
+ return result;
334
+ }
335
+
336
+ /**
337
+ * Evaluate an expression and return the raw numeric value.
338
+ * Used by watch expressions.
339
+ */
340
+ evaluateValue(expr) {
341
+ const exprPtr = this.wasmModule._malloc(expr.length + 1);
342
+ this.wasmModule.stringToUTF8(expr, exprPtr, expr.length + 1);
343
+ const result = this.wasmModule._evaluateExpression(exprPtr);
344
+ this.wasmModule._free(exprPtr);
345
+ return result;
346
+ }
347
+
348
+ // ---- WASM sync helpers ----
349
+
350
+ _syncWasmAdd(address, enabled) {
351
+ try {
352
+ this.wasmModule._addBreakpoint(address);
353
+ if (!enabled) {
354
+ this.wasmModule._enableBreakpoint(address, false);
355
+ }
356
+ } catch (e) {
357
+ /* ignore */
358
+ }
359
+ }
360
+
361
+ _syncWasmRemove(address) {
362
+ try {
363
+ this.wasmModule._removeBreakpoint(address);
364
+ } catch (e) {
365
+ /* ignore */
366
+ }
367
+ }
368
+
369
+ _syncWatchpointAdd(entry) {
370
+ if (this.wasmModule._addWatchpoint) {
371
+ try {
372
+ const typeMap = { read: 1, write: 2, readwrite: 3 };
373
+ this.wasmModule._addWatchpoint(
374
+ entry.address,
375
+ entry.endAddress ?? entry.address,
376
+ typeMap[entry.type] || 3,
377
+ );
378
+ } catch (e) {
379
+ /* ignore */
380
+ }
381
+ }
382
+ }
383
+
384
+ _syncWatchpointRemove(address) {
385
+ if (this.wasmModule._removeWatchpoint) {
386
+ try {
387
+ this.wasmModule._removeWatchpoint(address);
388
+ } catch (e) {
389
+ /* ignore */
390
+ }
391
+ }
392
+ }
393
+
394
+ /**
395
+ * Re-push all breakpoints and watchpoints from JS state to C++.
396
+ * Called after state import since importState() calls reset() which
397
+ * clears all WASM-side breakpoints.
398
+ */
399
+ resyncToWasm() {
400
+ for (const [address, entry] of this.breakpoints) {
401
+ if (entry.isTemp) continue;
402
+ if (entry.type === "exec") {
403
+ this._syncWasmAdd(address, entry.enabled);
404
+ } else {
405
+ this._syncWatchpointAdd(entry);
406
+ }
407
+ }
408
+ }
409
+
410
+ // ---- Persistence ----
411
+
412
+ save() {
413
+ try {
414
+ const data = [];
415
+ for (const [addr, entry] of this.breakpoints) {
416
+ if (entry.isTemp) continue;
417
+ data.push({
418
+ address: addr,
419
+ endAddress: entry.endAddress ?? addr,
420
+ name: entry.name || null,
421
+ enabled: entry.enabled,
422
+ condition: entry.condition,
423
+ conditionRules: entry.conditionRules,
424
+ hitTarget: entry.hitTarget,
425
+ type: entry.type,
426
+ });
427
+ }
428
+ localStorage.setItem(
429
+ BreakpointManager.STORAGE_KEY,
430
+ JSON.stringify(data),
431
+ );
432
+ } catch (e) {
433
+ console.warn("Failed to save breakpoints:", e);
434
+ }
435
+ }
436
+
437
+ load() {
438
+ try {
439
+ // Try new format first
440
+ const saved = localStorage.getItem(BreakpointManager.STORAGE_KEY);
441
+ if (saved) {
442
+ const data = JSON.parse(saved);
443
+ for (const entry of data) {
444
+ this.add(entry.address, {
445
+ endAddress: entry.endAddress,
446
+ name: entry.name,
447
+ enabled: entry.enabled,
448
+ condition: entry.condition,
449
+ conditionRules: entry.conditionRules,
450
+ hitTarget: entry.hitTarget,
451
+ type: entry.type,
452
+ });
453
+ }
454
+ return;
455
+ }
456
+
457
+ // Fall back to old format
458
+ const oldSaved = localStorage.getItem("a2e-breakpoints");
459
+ if (oldSaved) {
460
+ const addresses = JSON.parse(oldSaved);
461
+ for (const addr of addresses) {
462
+ this.add(addr);
463
+ }
464
+ // Migrate to new format
465
+ this.save();
466
+ localStorage.removeItem("a2e-breakpoints");
467
+ }
468
+ } catch (e) {
469
+ console.warn("Failed to load breakpoints:", e);
470
+ }
471
+ }
472
+ }