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
package/src/js/main.js ADDED
@@ -0,0 +1,653 @@
1
+ /*
2
+ * main.js - Main entry point and AppleIIeEmulator class
3
+ *
4
+ * Written by
5
+ * Mike Daley <michael_daley@icloud.com>
6
+ */
7
+
8
+ // CSS imports - bundled by Vite with content hashes for cache busting
9
+ import "../css/base.css";
10
+ import "../css/layout.css";
11
+ import "../css/monitor.css";
12
+ import "../css/disk-drives.css";
13
+ import "../css/hard-drive.css";
14
+ import "../css/controls.css";
15
+ import "../css/modals.css";
16
+ import "../css/debug-base.css";
17
+ import "../css/cpu-debugger.css";
18
+ import "../css/memory-windows.css";
19
+ import "../css/settings-windows.css";
20
+ import "../css/save-states.css";
21
+ import "../css/rule-builder.css";
22
+ import "../css/basic-editor.css";
23
+ import "../css/basic-debugger.css";
24
+ import "../css/assembler-editor.css";
25
+ import "../css/release-notes.css";
26
+ import "../css/file-explorer.css";
27
+ import "../css/documentation.css";
28
+ import "../css/window-switcher.css";
29
+ import "../css/responsive.css";
30
+
31
+ import { VERSION } from "./config/version.js";
32
+ import { DEFAULT_LAYOUT } from "./config/default-layout.js";
33
+ import { WebGLRenderer } from "./display/webgl-renderer.js";
34
+ import { AudioDriver } from "./audio/audio-driver.js";
35
+ import { InputHandler, TextSelection, JoystickWindow, MouseHandler, GamepadHandler } from "./input/index.js";
36
+ import { DiskManager } from "./disk-manager/index.js";
37
+ import { DiskDrivesWindow } from "./disk-manager/disk-drives-window.js";
38
+ import { HardDriveManager } from "./disk-manager/hard-drive-manager.js";
39
+ import { HardDriveWindow } from "./disk-manager/hard-drive-window.js";
40
+ import { FileExplorerWindow } from "./file-explorer/index.js";
41
+ import { DisplaySettingsWindow, ScreenWindow } from "./display/index.js";
42
+ import { DocumentationWindow, ReleaseNotesWindow } from "./help/index.js";
43
+ import { ReminderController } from "./ui/reminder-controller.js";
44
+ import { UIController } from "./ui/ui-controller.js";
45
+ import { ThemeManager } from "./ui/theme-manager.js";
46
+ import { showToast } from "./ui/toast.js";
47
+ import { SlotConfigurationWindow } from "./ui/slot-configuration-window.js";
48
+ import { WindowSwitcher } from "./ui/window-switcher.js";
49
+ import { StateManager } from "./state/state-manager.js";
50
+ import { SaveStatesWindow } from "./state/save-states-window.js";
51
+ import { AgentManager } from "./agent/index.js";
52
+ import {
53
+ WindowManager,
54
+ CPUDebuggerWindow,
55
+ SoftSwitchWindow,
56
+ MemoryBrowserWindow,
57
+ MemoryHeatMapWindow,
58
+ MemoryMapWindow,
59
+ StackViewerWindow,
60
+ ZeroPageWatchWindow,
61
+ MockingboardWindow,
62
+ MouseCardWindow,
63
+ BasicProgramWindow,
64
+ RuleBuilderWindow,
65
+ AssemblerEditorWindow,
66
+ TracePanelWindow,
67
+ } from "./debug/index.js";
68
+
69
+ class AppleIIeEmulator {
70
+ constructor() {
71
+ this.wasmModule = null;
72
+ this.renderer = null;
73
+ this.audioDriver = null;
74
+ this.inputHandler = null;
75
+ this.diskManager = null;
76
+ this.hardDriveManager = null;
77
+ this.fileExplorer = null;
78
+ this.windowManager = null;
79
+ this.displaySettings = null;
80
+ this.textSelection = null;
81
+ this.reminderController = null;
82
+ this.documentationWindow = null;
83
+ this.uiController = null;
84
+ this.stateManager = null;
85
+ this.mouseHandler = null;
86
+ this.themeManager = null;
87
+ this.agentManager = null;
88
+
89
+ this.running = false;
90
+ }
91
+
92
+ async init() {
93
+ // Apply theme before any rendering to prevent flash of wrong theme
94
+ this.themeManager = new ThemeManager();
95
+
96
+ this.showLoading(true);
97
+
98
+ try {
99
+ // Load WASM module - use global function loaded via script tag
100
+ this.wasmModule = await window.createA2EModule();
101
+
102
+ // Initialize emulator
103
+ this.wasmModule._init();
104
+
105
+ // Set up renderer
106
+ const canvas = document.getElementById("screen");
107
+ this.renderer = new WebGLRenderer(canvas);
108
+ await this.renderer.init();
109
+
110
+ // Set up audio driver (drives timing)
111
+ this.audioDriver = new AudioDriver(this.wasmModule);
112
+
113
+ // Connect audio-driven frame sync to rendering via rAF flag
114
+ // (avoids doing GPU texture uploads from the audio callback context,
115
+ // which causes stutters in Safari and Brave under heavy I/O)
116
+ this.frameReady = false;
117
+ this.audioDriver.onFrameReady = () => {
118
+ this.frameReady = true;
119
+ };
120
+
121
+ // Set up input handler
122
+ this.inputHandler = new InputHandler(this.wasmModule);
123
+ this.inputHandler.init();
124
+
125
+ // Set up mouse handler for Apple Mouse Interface Card
126
+ this.mouseHandler = new MouseHandler(this.wasmModule);
127
+ this.mouseHandler.init();
128
+
129
+ // Set up window manager
130
+ this.windowManager = new WindowManager();
131
+
132
+ // Set up file explorer (registered with window manager for proper z-index/focus)
133
+ this.fileExplorer = new FileExplorerWindow(this.wasmModule);
134
+ this.fileExplorer.create();
135
+ this.windowManager.register(this.fileExplorer);
136
+
137
+ // Create disk drives window first so DiskManager can find its DOM elements
138
+ const diskDrivesWindow = new DiskDrivesWindow();
139
+ diskDrivesWindow.create();
140
+ this.windowManager.register(diskDrivesWindow);
141
+
142
+ // Set up disk manager (must be after disk drives window is created)
143
+ this.diskManager = new DiskManager(this.wasmModule);
144
+ this.diskManager.init();
145
+ this.diskManager.onDiskLoaded = () => {
146
+ this.reminderController.dismissBasicReminder();
147
+ };
148
+
149
+ // Create hard drive window and manager
150
+ const hardDriveWindow = new HardDriveWindow();
151
+ hardDriveWindow.create();
152
+ this.windowManager.register(hardDriveWindow);
153
+
154
+ this.diskManager.fileExplorer = this.fileExplorer;
155
+
156
+ this.hardDriveManager = new HardDriveManager(this.wasmModule);
157
+ this.hardDriveManager.fileExplorer = this.fileExplorer;
158
+ this.hardDriveManager.init();
159
+
160
+
161
+ const cpuWindow = new CPUDebuggerWindow(this.wasmModule, () => this.isRunning());
162
+ cpuWindow.create();
163
+ this.windowManager.register(cpuWindow);
164
+ this.cpuDebuggerWindow = cpuWindow;
165
+
166
+ const ruleBuilderWindow = new RuleBuilderWindow();
167
+ ruleBuilderWindow.create();
168
+ this.windowManager.register(ruleBuilderWindow);
169
+
170
+ ruleBuilderWindow.onApply = (addr, condStr, rules) => {
171
+ cpuWindow.bpManager.setCondition(addr, condStr);
172
+ cpuWindow.bpManager.setConditionRules(addr, rules);
173
+ };
174
+ cpuWindow.setRuleBuilder(ruleBuilderWindow);
175
+
176
+ // BASIC breakpoint condition callback - will be wired after basicProgramWindow is created
177
+ ruleBuilderWindow.onApplyBasic = (key, condStr, rules) => {
178
+ if (this.basicProgramWindow) {
179
+ const bpMgr = this.basicProgramWindow.breakpointManager;
180
+ if (key === "__new_rule__") {
181
+ // New condition-only rule
182
+ bpMgr.addConditionRule(condStr, rules);
183
+ } else {
184
+ const [lineStr, stmtStr] = key.split(":");
185
+ const lineNum = parseInt(lineStr, 10);
186
+ const stmtIdx = parseInt(stmtStr, 10);
187
+ bpMgr.setCondition(lineNum, stmtIdx, condStr);
188
+ bpMgr.setConditionRules(lineNum, stmtIdx, rules);
189
+ }
190
+ this.basicProgramWindow.renderBreakpointList();
191
+ }
192
+ };
193
+
194
+ const switchWindow = new SoftSwitchWindow(this.wasmModule);
195
+ switchWindow.create();
196
+ this.windowManager.register(switchWindow);
197
+
198
+ // Set up display settings window (pass renderer for shader control, wasmModule for video settings)
199
+ this.displaySettings = new DisplaySettingsWindow(
200
+ this.renderer,
201
+ this.wasmModule,
202
+ );
203
+ this.displaySettings.create();
204
+ this.windowManager.register(this.displaySettings);
205
+
206
+ // Set up screen window (hosts the emulator canvas)
207
+ this.screenWindow = new ScreenWindow(this.renderer, null); // textSelection set later
208
+ this.screenWindow.create();
209
+ this.windowManager.register(this.screenWindow);
210
+
211
+ // Set up new debug windows
212
+ const memBrowserWindow = new MemoryBrowserWindow(this.wasmModule);
213
+ memBrowserWindow.create();
214
+ this.windowManager.register(memBrowserWindow);
215
+
216
+ const memHeatMapWindow = new MemoryHeatMapWindow(this.wasmModule);
217
+ memHeatMapWindow.create();
218
+ this.windowManager.register(memHeatMapWindow);
219
+
220
+ // Connect heat map to memory browser for click-to-jump
221
+ memHeatMapWindow.setJumpCallback((addr) => {
222
+ memBrowserWindow.jumpToAddress(addr);
223
+ this.windowManager.showWindow("memory-browser");
224
+ });
225
+
226
+ const memMapWindow = new MemoryMapWindow(this.wasmModule);
227
+ memMapWindow.create();
228
+ this.windowManager.register(memMapWindow);
229
+
230
+ const stackWindow = new StackViewerWindow(this.wasmModule);
231
+ stackWindow.create();
232
+ this.windowManager.register(stackWindow);
233
+
234
+ const zpWatchWindow = new ZeroPageWatchWindow(this.wasmModule);
235
+ zpWatchWindow.create();
236
+ this.windowManager.register(zpWatchWindow);
237
+
238
+ const joystickWindow = new JoystickWindow(this.wasmModule);
239
+ joystickWindow.create();
240
+ this.windowManager.register(joystickWindow);
241
+
242
+ this.gamepadHandler = new GamepadHandler(this.wasmModule, joystickWindow);
243
+ joystickWindow.gamepadHandler = this.gamepadHandler;
244
+
245
+ const mockingboardWindow = new MockingboardWindow(this.wasmModule);
246
+ mockingboardWindow.create();
247
+ this.windowManager.register(mockingboardWindow);
248
+
249
+ const mouseCardWindow = new MouseCardWindow(this.wasmModule);
250
+ mouseCardWindow.create();
251
+ this.windowManager.register(mouseCardWindow);
252
+
253
+ const tracePanelWindow = new TracePanelWindow(this.wasmModule);
254
+ tracePanelWindow.create();
255
+ this.windowManager.register(tracePanelWindow);
256
+
257
+ const basicProgramWindow = new BasicProgramWindow(
258
+ this.wasmModule,
259
+ this.inputHandler,
260
+ () => this.isRunning(),
261
+ );
262
+ basicProgramWindow.create();
263
+ this.windowManager.register(basicProgramWindow);
264
+ this.basicProgramWindow = basicProgramWindow;
265
+ basicProgramWindow.setRuleBuilder(ruleBuilderWindow);
266
+
267
+ const assemblerWindow = new AssemblerEditorWindow(this.wasmModule, cpuWindow.bpManager, () => this.isRunning(), cpuWindow);
268
+ assemblerWindow.create();
269
+ this.windowManager.register(assemblerWindow);
270
+
271
+ // Slot configuration window
272
+ const slotConfigWindow = new SlotConfigurationWindow(
273
+ this.wasmModule,
274
+ () => {
275
+ this.wasmModule._reset();
276
+ this.updateMouseHandlerState();
277
+ if (this.hardDriveManager) {
278
+ this.hardDriveManager.syncWithEmulatorState();
279
+ }
280
+ },
281
+ );
282
+ slotConfigWindow.create();
283
+ this.windowManager.register(slotConfigWindow);
284
+
285
+ // Release notes window
286
+ this.releaseNotesWindow = new ReleaseNotesWindow();
287
+ this.releaseNotesWindow.create();
288
+ this.windowManager.register(this.releaseNotesWindow);
289
+
290
+ // Release notes button in footer
291
+ const releaseNotesBtn = document.getElementById("btn-release-notes");
292
+ if (releaseNotesBtn) {
293
+ releaseNotesBtn.addEventListener("click", () => {
294
+ this.windowManager.toggleWindow("release-notes");
295
+ });
296
+ }
297
+
298
+ // Set up documentation window
299
+ this.documentationWindow = new DocumentationWindow();
300
+ this.documentationWindow.create();
301
+ this.windowManager.register(this.documentationWindow);
302
+
303
+ // Load saved window states (must be after all windows are registered)
304
+ this.windowManager.loadState();
305
+
306
+ // Save window states when page is closed
307
+ window.addEventListener("beforeunload", () => {
308
+ if (this.windowManager) {
309
+ this.windowManager.saveState();
310
+ }
311
+ });
312
+
313
+ // Start with TV static "no signal" since emulator is off
314
+ this.renderer.setNoSignal(true);
315
+
316
+ // Set up text selection for copying screen contents
317
+ this.textSelection = new TextSelection(canvas, this.wasmModule, this.renderer);
318
+
319
+ // Wire textSelection into screen window
320
+ this.screenWindow.textSelection = this.textSelection;
321
+
322
+ // Set up reminder controller
323
+ this.reminderController = new ReminderController();
324
+
325
+ // Apply display settings
326
+ this.displaySettings.applyAllSettings();
327
+
328
+ // Ensure ScreenWindow is visible (loadState may have already shown it)
329
+ if (!this.screenWindow.isVisible) {
330
+ this.screenWindow.show();
331
+ }
332
+ this.screenWindow.attachCanvas();
333
+
334
+ // Position/size windows for first-time users (no saved state)
335
+ this.windowManager.applyDefaultLayout(DEFAULT_LAYOUT);
336
+
337
+ // Set up window switcher (Ctrl+`)
338
+ this.windowSwitcher = new WindowSwitcher(this.windowManager);
339
+ this.windowSwitcher.create();
340
+
341
+ // Set up agent manager for MCP server connection
342
+ window.emulator = this;
343
+ this.agentManager = new AgentManager();
344
+
345
+ // Set up UI controller
346
+ this.uiController = new UIController({
347
+ emulator: this,
348
+ wasmModule: this.wasmModule,
349
+ audioDriver: this.audioDriver,
350
+ diskManager: this.diskManager,
351
+ fileExplorer: this.fileExplorer,
352
+ windowManager: this.windowManager,
353
+ screenWindow: this.screenWindow,
354
+ reminderController: this.reminderController,
355
+ inputHandler: this.inputHandler,
356
+ themeManager: this.themeManager,
357
+ windowSwitcher: this.windowSwitcher,
358
+ });
359
+ this.uiController.init();
360
+
361
+ // Set up state manager
362
+ this.stateManager = new StateManager({
363
+ emulator: this,
364
+ wasmModule: this.wasmModule,
365
+ uiController: this.uiController,
366
+ diskManager: this.diskManager,
367
+ reminderController: this.reminderController,
368
+ cpuDebuggerWindow: cpuWindow,
369
+ basicProgramWindow: this.basicProgramWindow,
370
+ });
371
+ this.stateManager.init();
372
+
373
+ // Save States window
374
+ const saveStatesWindow = new SaveStatesWindow(this.stateManager, this.uiController);
375
+ saveStatesWindow.create();
376
+ this.windowManager.register(saveStatesWindow);
377
+
378
+ // Keep autosave row current when the window is open
379
+ this.stateManager.onAutosave = () => {
380
+ if (saveStatesWindow.isVisible) {
381
+ saveStatesWindow.refreshAutosaveRow();
382
+ }
383
+ };
384
+
385
+ // Enable mouse handler if a mouse card is configured
386
+ this.updateMouseHandlerState();
387
+
388
+ // Start render loop
389
+ this.startRenderLoop();
390
+
391
+ this.showLoading(false);
392
+ this.reminderController.showPowerReminder(true);
393
+
394
+ console.log("Apple //e Emulator initialized");
395
+ } catch (error) {
396
+ console.error("Failed to initialize emulator:", error);
397
+ this.showLoading(false);
398
+ showToast("Failed to initialize emulator: " + error.message, "error");
399
+ }
400
+ }
401
+
402
+ /**
403
+ * Check slot configuration and enable/disable mouse handler accordingly
404
+ */
405
+ updateMouseHandlerState() {
406
+ if (!this.mouseHandler || !this.wasmModule._getSlotCard) return;
407
+ let mousePresent = false;
408
+ for (let slot = 1; slot <= 7; slot++) {
409
+ const ptr = this.wasmModule._getSlotCard(slot);
410
+ if (ptr) {
411
+ const name = this.wasmModule.UTF8ToString(ptr);
412
+ if (name === "mouse") {
413
+ mousePresent = true;
414
+ break;
415
+ }
416
+ }
417
+ }
418
+ if (mousePresent) {
419
+ this.mouseHandler.enable();
420
+ } else {
421
+ this.mouseHandler.disable();
422
+ }
423
+ }
424
+
425
+ /**
426
+ * Check if the emulator is running
427
+ * @returns {boolean}
428
+ */
429
+ isRunning() {
430
+ return this.running;
431
+ }
432
+
433
+ start() {
434
+ if (this.running) return;
435
+
436
+ if (this.inputHandler) this.inputHandler.cancelPaste();
437
+ this.wasmModule._reset();
438
+ this.running = true;
439
+ this.renderer.setNoSignal(false);
440
+ this.audioDriver.start();
441
+ if (this.uiController) {
442
+ this.uiController.updatePowerButton(true);
443
+ }
444
+ console.log("Emulator powered on");
445
+ }
446
+
447
+ stop() {
448
+ if (!this.running) return;
449
+
450
+ this.running = false;
451
+ this.audioDriver.stop();
452
+
453
+ if (this.wasmModule._stopDiskMotor) {
454
+ this.wasmModule._stopDiskMotor();
455
+ }
456
+
457
+ this.renderer.setNoSignal(true);
458
+ if (this.uiController) {
459
+ this.uiController.updatePowerButton(false);
460
+ }
461
+ console.log("Emulator powered off");
462
+ }
463
+
464
+ renderFrame() {
465
+ const fbPtr = this.wasmModule._getFramebuffer();
466
+ const fbSize = this.wasmModule._getFramebufferSize();
467
+ const framebuffer = new Uint8Array(
468
+ this.wasmModule.HEAPU8.buffer,
469
+ fbPtr,
470
+ fbSize,
471
+ );
472
+
473
+ this.renderer.updateTexture(framebuffer);
474
+ this.renderer.draw();
475
+ }
476
+
477
+ startRenderLoop() {
478
+ const render = () => {
479
+ this.windowManager.updateAll(this.wasmModule);
480
+ this.diskManager.drivesWindowVisible = this.windowManager.isWindowVisible('disk-drives');
481
+ this.diskManager.updateLEDs();
482
+ if (this.hardDriveManager) {
483
+ this.hardDriveManager.updateLEDs();
484
+ }
485
+
486
+ // Beam crosshair overlay — only when CPU debugger is open and CPU is paused
487
+ const isPaused = this.running && this.wasmModule._isPaused();
488
+ if (isPaused && this.cpuDebuggerWindow && this.cpuDebuggerWindow.isVisible) {
489
+ const scanline = this.wasmModule._getBeamScanline();
490
+ const hPos = this.wasmModule._getBeamHPos();
491
+ // Map beam Y to center of scanline band (0–191 visible, ≥192 is VBL)
492
+ this.renderer.setParam("beamY", scanline < 192 ? (scanline + 0.5) / 192.0 : -1.0);
493
+ // Map beam X to leading edge of column (hPos 25–64 → columns 0–39)
494
+ this.renderer.setParam("beamX", hPos >= 25 ? (hPos - 25) / 40.0 : -1.0);
495
+ } else {
496
+ this.renderer.setParam("beamY", -1.0);
497
+ this.renderer.setParam("beamX", -1.0);
498
+ }
499
+
500
+ if (this.frameReady) {
501
+ this.frameReady = false;
502
+ this.renderFrame();
503
+ } else if (!this.running || isPaused) {
504
+ // Force a complete re-render of the framebuffer from current video
505
+ // memory so the display shows the full screen after stepping/pausing,
506
+ // not a partial frame based on beam position.
507
+ if (isPaused) {
508
+ this.wasmModule._forceRenderFrame();
509
+ this.renderFrame();
510
+ } else {
511
+ this.renderer.draw();
512
+ }
513
+ }
514
+
515
+ requestAnimationFrame(render);
516
+ };
517
+
518
+ requestAnimationFrame(render);
519
+ }
520
+
521
+ showLoading(show) {
522
+ const loading = document.getElementById("loading");
523
+ if (show) {
524
+ loading.classList.remove("hidden");
525
+ } else {
526
+ loading.classList.add("hidden");
527
+ }
528
+ }
529
+
530
+ /**
531
+ * Clean up resources and remove event listeners.
532
+ */
533
+ destroy() {
534
+ if (this.running) {
535
+ this.stop();
536
+ }
537
+
538
+ if (this.stateManager) {
539
+ this.stateManager.destroy();
540
+ this.stateManager = null;
541
+ }
542
+
543
+ if (this.textSelection) {
544
+ this.textSelection.destroy();
545
+ this.textSelection = null;
546
+ }
547
+
548
+ if (this.windowManager) {
549
+ this.windowManager.saveState();
550
+ this.windowManager = null;
551
+ }
552
+
553
+ if (this.audioDriver) {
554
+ this.audioDriver.stop();
555
+ this.audioDriver = null;
556
+ }
557
+
558
+ if (this.themeManager) {
559
+ this.themeManager.destroy();
560
+ this.themeManager = null;
561
+ }
562
+
563
+ if (this.agentManager) {
564
+ this.agentManager.disconnect();
565
+ this.agentManager = null;
566
+ }
567
+
568
+ if (this.gamepadHandler) {
569
+ this.gamepadHandler.destroy();
570
+ this.gamepadHandler = null;
571
+ }
572
+
573
+ this.renderer = null;
574
+ this.diskManager = null;
575
+ this.hardDriveManager = null;
576
+ if (this.fileExplorer) {
577
+ this.fileExplorer.destroy();
578
+ this.fileExplorer = null;
579
+ }
580
+ this.inputHandler = null;
581
+ this.reminderController = null;
582
+ this.uiController = null;
583
+
584
+ console.log("Apple //e Emulator destroyed");
585
+ }
586
+ }
587
+
588
+ // Register service worker for offline support only when installed as PWA
589
+ const isInstalled = window.matchMedia("(display-mode: standalone)").matches || navigator.standalone;
590
+ if ("serviceWorker" in navigator && isInstalled) {
591
+ window.addEventListener("load", () => {
592
+ navigator.serviceWorker
593
+ .register("/sw.js")
594
+ .then((registration) => {
595
+ console.log("Service Worker registered:", registration.scope);
596
+
597
+ // Check for updates immediately on load
598
+ registration.update().catch((err) => {
599
+ console.log("Service Worker update check failed:", err);
600
+ });
601
+
602
+ // Handle new service worker installation
603
+ registration.addEventListener("updatefound", () => {
604
+ const newWorker = registration.installing;
605
+ if (newWorker) {
606
+ newWorker.addEventListener("statechange", () => {
607
+ if (newWorker.state === "installed") {
608
+ if (navigator.serviceWorker.controller) {
609
+ // New version available - show badge on Help button
610
+ console.log("New version available - badge shown on Help button");
611
+ const helpBtn = document.getElementById("btn-help-menu");
612
+ if (helpBtn) {
613
+ helpBtn.classList.add("update-available");
614
+ }
615
+ } else {
616
+ // First install - no reload needed
617
+ console.log("App cached for offline use");
618
+ }
619
+ }
620
+ });
621
+ }
622
+ });
623
+ })
624
+ .catch((error) => {
625
+ console.log("Service Worker registration failed:", error);
626
+ });
627
+ });
628
+ }
629
+
630
+ // Initialize when DOM is ready
631
+ document.addEventListener("DOMContentLoaded", () => {
632
+ // Display version in header
633
+ const versionEl = document.getElementById("app-version");
634
+ if (versionEl) {
635
+ versionEl.textContent = `v${VERSION}`;
636
+ }
637
+
638
+ const emulator = new AppleIIeEmulator();
639
+ emulator.init();
640
+
641
+ // Make emulator accessible globally for debugging
642
+ window.a2e = emulator;
643
+
644
+ // Helper to toggle Mockingboard debug logging from console
645
+ window.mbDebug = (enabled = true) => {
646
+ if (emulator.wasmModule && emulator.wasmModule._setMockingboardDebugLogging) {
647
+ emulator.wasmModule._setMockingboardDebugLogging(enabled);
648
+ console.log(`Mockingboard debug logging ${enabled ? "enabled" : "disabled"}`);
649
+ } else {
650
+ console.log("Mockingboard debug logging not available");
651
+ }
652
+ };
653
+ });
@@ -0,0 +1,15 @@
1
+ /*
2
+ * index.js - State subsystem initialization and exports
3
+ *
4
+ * Written by
5
+ * Mike Daley <michael_daley@icloud.com>
6
+ */
7
+
8
+ export { StateManager } from "./state-manager.js";
9
+ export {
10
+ saveStateToStorage,
11
+ loadStateFromStorage,
12
+ clearStateFromStorage,
13
+ hasSavedState,
14
+ getSavedStateTimestamp,
15
+ } from "./state-persistence.js";