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,444 @@
1
+ /*
2
+ * disk_image_builder.cpp - Programmatic disk image builders
3
+ */
4
+
5
+ #include "disk_image_builder.hpp"
6
+ #include <algorithm>
7
+ #include <cstring>
8
+
9
+ namespace test {
10
+
11
+ // ==================== DOS33DiskBuilder ====================
12
+
13
+ DOS33DiskBuilder::DOS33DiskBuilder() : data_(DISK_SIZE, 0) {
14
+ initVTOC();
15
+ initCatalog();
16
+ }
17
+
18
+ int DOS33DiskBuilder::getSectorOffset(int track, int sector) const {
19
+ return (track * SECTORS + sector) * SECTOR_SIZE;
20
+ }
21
+
22
+ void DOS33DiskBuilder::initVTOC() {
23
+ // VTOC is at track 17, sector 0
24
+ int offset = getSectorOffset(17, 0);
25
+
26
+ data_[offset + 0x01] = 17; // Catalog track
27
+ data_[offset + 0x02] = 15; // Catalog sector
28
+ data_[offset + 0x03] = 3; // DOS version (3.3)
29
+ data_[offset + 0x06] = 254; // Volume number
30
+ data_[offset + 0x27] = 122; // Max track/sector pairs per TS list
31
+ data_[offset + 0x30] = 17; // Last track allocated
32
+ data_[offset + 0x31] = 1; // Direction of allocation (+1)
33
+ data_[offset + 0x34] = 35; // Number of tracks
34
+ data_[offset + 0x35] = 16; // Sectors per track
35
+ data_[offset + 0x36] = 0; // Bytes per sector low
36
+ data_[offset + 0x37] = 1; // Bytes per sector high (256)
37
+
38
+ // Free sector bitmap - mark all sectors as free
39
+ // Each track gets 4 bytes starting at offset 0x38
40
+ for (int t = 0; t < 35; t++) {
41
+ int bitmapOffset = offset + 0x38 + t * 4;
42
+ data_[bitmapOffset + 0] = 0xFF; // Sectors 0-7 free
43
+ data_[bitmapOffset + 1] = 0xFF; // Sectors 8-15 free
44
+ data_[bitmapOffset + 2] = 0x00;
45
+ data_[bitmapOffset + 3] = 0x00;
46
+ }
47
+
48
+ // Mark VTOC track (17) sectors as used
49
+ int vtocBitmap = offset + 0x38 + 17 * 4;
50
+ data_[vtocBitmap + 0] = 0x00;
51
+ data_[vtocBitmap + 1] = 0x00;
52
+ }
53
+
54
+ void DOS33DiskBuilder::initCatalog() {
55
+ // First catalog sector: track 17, sector 15
56
+ // Just leave it zeroed - that marks it as having no entries
57
+ // The next catalog link is already 0,0 which means end of catalog
58
+ }
59
+
60
+ bool DOS33DiskBuilder::allocateSector(int& track, int& sector) {
61
+ if (nextAllocTrack_ >= TRACKS) return false;
62
+ track = nextAllocTrack_;
63
+ sector = nextAllocSector_;
64
+ nextAllocSector_++;
65
+ if (nextAllocSector_ >= SECTORS) {
66
+ nextAllocSector_ = 0;
67
+ nextAllocTrack_++;
68
+ if (nextAllocTrack_ == 17) nextAllocTrack_ = 18; // Skip catalog track
69
+ }
70
+ return true;
71
+ }
72
+
73
+ void DOS33DiskBuilder::writeSector(int track, int sector, const uint8_t* sdata, int len) {
74
+ int offset = getSectorOffset(track, sector);
75
+ int copyLen = std::min(len, SECTOR_SIZE);
76
+ std::memcpy(&data_[offset], sdata, copyLen);
77
+ }
78
+
79
+ bool DOS33DiskBuilder::addFile(const std::string& name, uint8_t fileType,
80
+ const uint8_t* fileData, int dataLen, bool locked) {
81
+ // Allocate a track/sector list sector
82
+ int tsTrack, tsSector;
83
+ if (!allocateSector(tsTrack, tsSector)) return false;
84
+
85
+ int tsOffset = getSectorOffset(tsTrack, tsSector);
86
+ // TS list: bytes 0x01-0x02 = next TS list (0,0 = none)
87
+ // Pairs start at byte 0x0C
88
+
89
+ // Write data sectors
90
+ int remaining = dataLen;
91
+ const uint8_t* ptr = fileData;
92
+ int pairIdx = 0;
93
+ int sectorCount = 0;
94
+
95
+ while (remaining > 0 && pairIdx < 122) {
96
+ int dataTrack, dataSector;
97
+ if (!allocateSector(dataTrack, dataSector)) return false;
98
+
99
+ int writeLen = std::min(remaining, SECTOR_SIZE);
100
+ writeSector(dataTrack, dataSector, ptr, writeLen);
101
+
102
+ // Record in TS list
103
+ data_[tsOffset + 0x0C + pairIdx * 2] = dataTrack;
104
+ data_[tsOffset + 0x0C + pairIdx * 2 + 1] = dataSector;
105
+
106
+ ptr += writeLen;
107
+ remaining -= writeLen;
108
+ pairIdx++;
109
+ sectorCount++;
110
+ }
111
+
112
+ // Add catalog entry
113
+ int catOffset = getSectorOffset(catalogTrack_, catalogSector_);
114
+ int entryOffset = catOffset + 0x0B + catalogEntryIndex_ * 0x23;
115
+
116
+ // First TS list track/sector
117
+ data_[entryOffset + 0x00] = tsTrack;
118
+ data_[entryOffset + 0x01] = tsSector;
119
+
120
+ // File type with locked flag
121
+ data_[entryOffset + 0x02] = (locked ? 0x80 : 0x00) | fileType;
122
+
123
+ // Filename (30 bytes, space-padded, high bit set)
124
+ for (int i = 0; i < 30; i++) {
125
+ if (i < static_cast<int>(name.size())) {
126
+ data_[entryOffset + 0x03 + i] = name[i] | 0x80;
127
+ } else {
128
+ data_[entryOffset + 0x03 + i] = ' ' | 0x80;
129
+ }
130
+ }
131
+
132
+ // Sector count
133
+ data_[entryOffset + 0x21] = sectorCount & 0xFF;
134
+ data_[entryOffset + 0x22] = (sectorCount >> 8) & 0xFF;
135
+
136
+ catalogEntryIndex_++;
137
+ if (catalogEntryIndex_ >= 7) {
138
+ // Would need another catalog sector - for testing, 7 entries is enough
139
+ catalogEntryIndex_ = 0;
140
+ catalogSector_--;
141
+ }
142
+
143
+ return true;
144
+ }
145
+
146
+ // ==================== ProDOSDiskBuilder ====================
147
+
148
+ ProDOSDiskBuilder::ProDOSDiskBuilder(const std::string& volumeName)
149
+ : data_(DISK_SIZE, 0), volumeName_(volumeName) {
150
+ initVolumeDirectory();
151
+ initBitmap();
152
+ }
153
+
154
+ void ProDOSDiskBuilder::readBlock(int blockNum, uint8_t* out) const {
155
+ // ProDOS block to physical sector mapping for 140K disks
156
+ // Block N maps to track N/8, sectors interleaved
157
+ static const uint8_t INTERLEAVE[16] = {
158
+ 0, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 15
159
+ };
160
+
161
+ int track = blockNum / 8;
162
+ int blockInTrack = blockNum % 8;
163
+ int sector1 = INTERLEAVE[blockInTrack * 2];
164
+ int sector2 = INTERLEAVE[blockInTrack * 2 + 1];
165
+
166
+ int off1 = (track * 16 + sector1) * 256;
167
+ int off2 = (track * 16 + sector2) * 256;
168
+
169
+ std::memcpy(out, &data_[off1], 256);
170
+ std::memcpy(out + 256, &data_[off2], 256);
171
+ }
172
+
173
+ void ProDOSDiskBuilder::writeBlock(int blockNum, const uint8_t* blockData, int len) {
174
+ // For simplicity, store blocks in ProDOS order (linear)
175
+ // Since we're building images for testing the ProDOS parser,
176
+ // we write in ProDOS-order format (blockNum * 512)
177
+ int offset = blockNum * BLOCK_SIZE;
178
+ if (offset + len > DISK_SIZE) return;
179
+ int copyLen = std::min(len, BLOCK_SIZE);
180
+ std::memcpy(&data_[offset], blockData, copyLen);
181
+ }
182
+
183
+ void ProDOSDiskBuilder::initVolumeDirectory() {
184
+ // Block 2 = volume directory header
185
+ uint8_t block[BLOCK_SIZE] = {};
186
+
187
+ // Prev/next directory block pointers
188
+ block[0] = 0; // Previous block (none for volume dir)
189
+ block[1] = 0;
190
+ block[2] = 3; // Next block
191
+ block[3] = 0;
192
+
193
+ // Volume directory header entry at offset 4
194
+ int nameLen = std::min(static_cast<int>(volumeName_.size()), 15);
195
+ block[4] = 0xF0 | nameLen; // Storage type F = volume header, name length
196
+
197
+ for (int i = 0; i < nameLen; i++) {
198
+ block[5 + i] = volumeName_[i] & 0x7F;
199
+ }
200
+
201
+ // Creation date/time (leave zeroed)
202
+ // Access byte
203
+ block[0x22] = 0xC3; // Full access
204
+
205
+ // Entry length
206
+ block[0x23] = 0x27; // 39 bytes per entry
207
+
208
+ // Entries per block
209
+ block[0x24] = 0x0D; // 13 entries per block
210
+
211
+ // File count
212
+ block[0x25] = 0;
213
+ block[0x26] = 0;
214
+
215
+ // Bitmap pointer
216
+ block[0x27] = 6; // Block 6
217
+ block[0x28] = 0;
218
+
219
+ // Total blocks
220
+ block[0x29] = 0x18; // 280 blocks (0x118)
221
+ block[0x2A] = 0x01;
222
+
223
+ writeBlock(2, block, BLOCK_SIZE);
224
+
225
+ // Block 3 - continuation of volume directory (empty)
226
+ uint8_t block3[BLOCK_SIZE] = {};
227
+ block3[0] = 2; block3[1] = 0; // Previous = block 2
228
+ block3[2] = 4; block3[3] = 0; // Next = block 4
229
+ writeBlock(3, block3, BLOCK_SIZE);
230
+
231
+ // Block 4 - continuation (empty)
232
+ uint8_t block4[BLOCK_SIZE] = {};
233
+ block4[0] = 3; block4[1] = 0; // Previous = block 3
234
+ block4[2] = 5; block4[3] = 0; // Next = block 5
235
+ writeBlock(4, block4, BLOCK_SIZE);
236
+
237
+ // Block 5 - last dir block
238
+ uint8_t block5[BLOCK_SIZE] = {};
239
+ block5[0] = 4; block5[1] = 0; // Previous = block 4
240
+ block5[2] = 0; block5[3] = 0; // Next = none
241
+ writeBlock(5, block5, BLOCK_SIZE);
242
+ }
243
+
244
+ void ProDOSDiskBuilder::initBitmap() {
245
+ // Volume bitmap at block 6
246
+ // 280 blocks = 35 bytes, each bit = 1 block (1=free, 0=used)
247
+ uint8_t bitmap[BLOCK_SIZE] = {};
248
+
249
+ // Mark all blocks as free initially
250
+ for (int i = 0; i < 35; i++) {
251
+ bitmap[i] = 0xFF;
252
+ }
253
+
254
+ // Mark blocks 0-9 as used (boot, volume dir, bitmap)
255
+ bitmap[0] = 0x00; // Blocks 0-7 used
256
+ bitmap[1] = 0x3F; // Blocks 8-9 used, 10-15 free
257
+
258
+ writeBlock(6, bitmap, BLOCK_SIZE);
259
+ }
260
+
261
+ bool ProDOSDiskBuilder::allocateBlock(int& blockNum) {
262
+ if (nextAllocBlock_ >= 280) return false;
263
+ blockNum = nextAllocBlock_++;
264
+ return true;
265
+ }
266
+
267
+ bool ProDOSDiskBuilder::addFile(const std::string& name, uint8_t fileType,
268
+ uint16_t auxType, const uint8_t* fileData, int dataLen) {
269
+ // Allocate block for file data (seedling - single block for small files)
270
+ int dataBlock;
271
+ if (!allocateBlock(dataBlock)) return false;
272
+
273
+ // Write file data
274
+ uint8_t blockBuf[BLOCK_SIZE] = {};
275
+ int writeLen = std::min(dataLen, BLOCK_SIZE);
276
+ if (fileData && writeLen > 0) {
277
+ std::memcpy(blockBuf, fileData, writeLen);
278
+ }
279
+ writeBlock(dataBlock, blockBuf, BLOCK_SIZE);
280
+
281
+ // Add directory entry
282
+ // Directory entries start at offset 4 + entry * 0x27 within a block
283
+ // First entry in block 2 is the volume header, so file entries start at index 1
284
+ int entryBlock = 2 + (dirEntryCount_ + 1) / 13;
285
+ int entryInBlock = (dirEntryCount_ + 1) % 13;
286
+ if (entryInBlock == 0 && entryBlock > 2) {
287
+ entryInBlock = 0;
288
+ }
289
+
290
+ // Read the directory block
291
+ int blockOffset = entryBlock * BLOCK_SIZE;
292
+ int entryOffset = blockOffset + 4 + entryInBlock * 0x27;
293
+
294
+ int nameLen = std::min(static_cast<int>(name.size()), 15);
295
+ data_[entryOffset] = 0x10 | nameLen; // Storage type 1 = seedling
296
+
297
+ for (int i = 0; i < nameLen; i++) {
298
+ data_[entryOffset + 1 + i] = name[i] & 0x7F;
299
+ }
300
+
301
+ // File type
302
+ data_[entryOffset + 0x10] = fileType;
303
+
304
+ // Key pointer (data block)
305
+ data_[entryOffset + 0x11] = dataBlock & 0xFF;
306
+ data_[entryOffset + 0x12] = (dataBlock >> 8) & 0xFF;
307
+
308
+ // Blocks used
309
+ data_[entryOffset + 0x13] = 1;
310
+ data_[entryOffset + 0x14] = 0;
311
+
312
+ // EOF
313
+ data_[entryOffset + 0x15] = dataLen & 0xFF;
314
+ data_[entryOffset + 0x16] = (dataLen >> 8) & 0xFF;
315
+ data_[entryOffset + 0x17] = (dataLen >> 16) & 0xFF;
316
+
317
+ // Aux type
318
+ data_[entryOffset + 0x1F] = auxType & 0xFF;
319
+ data_[entryOffset + 0x20] = (auxType >> 8) & 0xFF;
320
+
321
+ // Access
322
+ data_[entryOffset + 0x1E] = 0xC3;
323
+
324
+ // Update file count in volume header
325
+ int volOffset = 2 * BLOCK_SIZE + 4;
326
+ dirEntryCount_++;
327
+ data_[volOffset + 0x21] = dirEntryCount_ & 0xFF;
328
+ data_[volOffset + 0x22] = (dirEntryCount_ >> 8) & 0xFF;
329
+
330
+ return true;
331
+ }
332
+
333
+ // ==================== PascalDiskBuilder ====================
334
+
335
+ PascalDiskBuilder::PascalDiskBuilder(const std::string& volumeName)
336
+ : data_(DISK_SIZE, 0), volumeName_(volumeName) {
337
+ initVolumeHeader();
338
+ }
339
+
340
+ void PascalDiskBuilder::initVolumeHeader() {
341
+ // Pascal directory is blocks 2-5 (2048 bytes)
342
+ // Volume header is the first 26-byte entry in block 2
343
+ // For testing, we write in ProDOS block order
344
+
345
+ int offset = 2 * BLOCK_SIZE; // Block 2
346
+
347
+ // Entry 0: Volume header
348
+ data_[offset + 0] = 0; // firstBlock = 0
349
+ data_[offset + 1] = 0;
350
+ data_[offset + 2] = 6; // nextBlock (first free block)
351
+ data_[offset + 3] = 0;
352
+ data_[offset + 4] = 0; // fileType = 0 (volume)
353
+ data_[offset + 5] = 0;
354
+
355
+ // Volume name (Pascal format: length byte + chars)
356
+ int nameLen = std::min(static_cast<int>(volumeName_.size()), 7);
357
+ data_[offset + 6] = nameLen;
358
+ for (int i = 0; i < nameLen; i++) {
359
+ data_[offset + 7 + i] = volumeName_[i];
360
+ }
361
+
362
+ // Total blocks
363
+ data_[offset + 14] = 0x18; // 280 blocks (low)
364
+ data_[offset + 15] = 0x01; // (high)
365
+
366
+ // File count (will be updated as files are added)
367
+ data_[offset + 16] = 0;
368
+ data_[offset + 17] = 0;
369
+
370
+ // Last access date (leave 0)
371
+ // Most recent date set
372
+ data_[offset + 20] = 0;
373
+ data_[offset + 21] = 0;
374
+ }
375
+
376
+ bool PascalDiskBuilder::addFile(const std::string& name, uint8_t fileType,
377
+ const uint8_t* fileData, int dataLen) {
378
+ int blocksNeeded = (dataLen + BLOCK_SIZE - 1) / BLOCK_SIZE;
379
+ if (blocksNeeded == 0) blocksNeeded = 1;
380
+ if (nextBlock_ + blocksNeeded > 280) return false;
381
+
382
+ int startBlock = nextBlock_;
383
+
384
+ // Write file data
385
+ for (int i = 0; i < blocksNeeded; i++) {
386
+ int blockOffset = (nextBlock_ + i) * BLOCK_SIZE;
387
+ int remaining = dataLen - i * BLOCK_SIZE;
388
+ int writeLen = std::min(remaining, BLOCK_SIZE);
389
+ if (writeLen > 0 && fileData) {
390
+ std::memcpy(&data_[blockOffset], fileData + i * BLOCK_SIZE, writeLen);
391
+ }
392
+ }
393
+
394
+ nextBlock_ += blocksNeeded;
395
+
396
+ // Add directory entry (26 bytes each, starting at entry 1 in block 2)
397
+ dirEntryCount_++;
398
+ int entryOffset = 2 * BLOCK_SIZE + dirEntryCount_ * 26;
399
+
400
+ data_[entryOffset + 0] = startBlock & 0xFF;
401
+ data_[entryOffset + 1] = (startBlock >> 8) & 0xFF;
402
+ data_[entryOffset + 2] = nextBlock_ & 0xFF;
403
+ data_[entryOffset + 3] = (nextBlock_ >> 8) & 0xFF;
404
+ data_[entryOffset + 4] = fileType;
405
+ data_[entryOffset + 5] = 0;
406
+
407
+ int nameLen = std::min(static_cast<int>(name.size()), 15);
408
+ data_[entryOffset + 6] = nameLen;
409
+ for (int i = 0; i < nameLen; i++) {
410
+ data_[entryOffset + 7 + i] = name[i];
411
+ }
412
+
413
+ // Bytes in last block
414
+ int bytesInLast = dataLen % BLOCK_SIZE;
415
+ if (bytesInLast == 0 && dataLen > 0) bytesInLast = BLOCK_SIZE;
416
+ data_[entryOffset + 22] = bytesInLast & 0xFF;
417
+ data_[entryOffset + 23] = (bytesInLast >> 8) & 0xFF;
418
+
419
+ // Update volume header file count
420
+ int volOffset = 2 * BLOCK_SIZE;
421
+ data_[volOffset + 16] = dirEntryCount_ & 0xFF;
422
+ data_[volOffset + 17] = (dirEntryCount_ >> 8) & 0xFF;
423
+
424
+ // Note: Do NOT update the nextBlock field in the volume header.
425
+ // That field must remain 6 (first block after the 4-block directory area)
426
+ // as required by the Pascal filesystem validator.
427
+
428
+ return true;
429
+ }
430
+
431
+ // ==================== BlockImageBuilder ====================
432
+
433
+ BlockImageBuilder::BlockImageBuilder(int totalBlocks)
434
+ : data_(totalBlocks * BLOCK_SIZE, 0) {
435
+ }
436
+
437
+ void BlockImageBuilder::writeBlock(int blockNum, const uint8_t* blockData, int len) {
438
+ int offset = blockNum * BLOCK_SIZE;
439
+ if (offset + len > static_cast<int>(data_.size())) return;
440
+ int copyLen = std::min(len, BLOCK_SIZE);
441
+ std::memcpy(&data_[offset], blockData, copyLen);
442
+ }
443
+
444
+ } // namespace test
@@ -0,0 +1,141 @@
1
+ /*
2
+ * disk_image_builder.hpp - Programmatic disk image builders for testing
3
+ */
4
+
5
+ #pragma once
6
+
7
+ #include <array>
8
+ #include <cstdint>
9
+ #include <cstring>
10
+ #include <string>
11
+ #include <vector>
12
+
13
+ namespace test {
14
+
15
+ /**
16
+ * DOS33DiskBuilder - Build minimal DOS 3.3 disk images for testing
17
+ *
18
+ * Creates 143360-byte images with a valid VTOC and catalog.
19
+ */
20
+ class DOS33DiskBuilder {
21
+ public:
22
+ static constexpr int TRACKS = 35;
23
+ static constexpr int SECTORS = 16;
24
+ static constexpr int SECTOR_SIZE = 256;
25
+ static constexpr int DISK_SIZE = TRACKS * SECTORS * SECTOR_SIZE; // 143360
26
+
27
+ DOS33DiskBuilder();
28
+
29
+ // Write raw data to a specific sector
30
+ void writeSector(int track, int sector, const uint8_t* data, int len);
31
+
32
+ // Add a file to the catalog. fileType: 0x00=T, 0x01=I, 0x02=A, 0x04=B
33
+ // Data is written sequentially to available sectors.
34
+ // Returns true on success.
35
+ bool addFile(const std::string& name, uint8_t fileType,
36
+ const uint8_t* data, int dataLen, bool locked = false);
37
+
38
+ // Get the built disk image
39
+ const std::vector<uint8_t>& build() const { return data_; }
40
+ const uint8_t* data() const { return data_.data(); }
41
+ size_t size() const { return data_.size(); }
42
+
43
+ private:
44
+ std::vector<uint8_t> data_;
45
+
46
+ // Track/sector allocation
47
+ int nextAllocTrack_ = 20; // Start allocating from track 20
48
+ int nextAllocSector_ = 0;
49
+
50
+ // Catalog state
51
+ int catalogTrack_ = 17;
52
+ int catalogSector_ = 15;
53
+ int catalogEntryIndex_ = 0; // 0-6 entries per sector
54
+
55
+ int getSectorOffset(int track, int sector) const;
56
+ bool allocateSector(int& track, int& sector);
57
+ void initVTOC();
58
+ void initCatalog();
59
+ };
60
+
61
+ /**
62
+ * ProDOSDiskBuilder - Build minimal ProDOS disk images for testing
63
+ */
64
+ class ProDOSDiskBuilder {
65
+ public:
66
+ static constexpr int BLOCK_SIZE = 512;
67
+ static constexpr int DISK_SIZE = 143360; // 280 blocks
68
+
69
+ ProDOSDiskBuilder(const std::string& volumeName = "TEST");
70
+
71
+ // Write raw data to a block
72
+ void writeBlock(int blockNum, const uint8_t* data, int len);
73
+
74
+ // Add a file to the volume directory
75
+ // Returns true on success.
76
+ bool addFile(const std::string& name, uint8_t fileType, uint16_t auxType,
77
+ const uint8_t* data, int dataLen);
78
+
79
+ const std::vector<uint8_t>& build() const { return data_; }
80
+ const uint8_t* data() const { return data_.data(); }
81
+ size_t size() const { return data_.size(); }
82
+
83
+ private:
84
+ std::vector<uint8_t> data_;
85
+ std::string volumeName_;
86
+
87
+ int nextAllocBlock_ = 10;
88
+ int dirEntryCount_ = 0;
89
+
90
+ void readBlock(int blockNum, uint8_t* out) const;
91
+ void initVolumeDirectory();
92
+ void initBitmap();
93
+ bool allocateBlock(int& blockNum);
94
+ };
95
+
96
+ /**
97
+ * PascalDiskBuilder - Build minimal Apple Pascal disk images for testing
98
+ */
99
+ class PascalDiskBuilder {
100
+ public:
101
+ static constexpr int BLOCK_SIZE = 512;
102
+ static constexpr int DISK_SIZE = 143360;
103
+
104
+ PascalDiskBuilder(const std::string& volumeName = "TEST");
105
+
106
+ bool addFile(const std::string& name, uint8_t fileType,
107
+ const uint8_t* data, int dataLen);
108
+
109
+ const std::vector<uint8_t>& build() const { return data_; }
110
+ const uint8_t* data() const { return data_.data(); }
111
+ size_t size() const { return data_.size(); }
112
+
113
+ private:
114
+ std::vector<uint8_t> data_;
115
+ std::string volumeName_;
116
+ int nextBlock_ = 6; // First usable block after directory
117
+ int dirEntryCount_ = 0;
118
+
119
+ void initVolumeHeader();
120
+ };
121
+
122
+ /**
123
+ * BlockImageBuilder - Build variable-size HDV block device images
124
+ */
125
+ class BlockImageBuilder {
126
+ public:
127
+ static constexpr int BLOCK_SIZE = 512;
128
+
129
+ explicit BlockImageBuilder(int totalBlocks = 65535);
130
+
131
+ void writeBlock(int blockNum, const uint8_t* data, int len);
132
+
133
+ const std::vector<uint8_t>& build() const { return data_; }
134
+ const uint8_t* data() const { return data_.data(); }
135
+ size_t size() const { return data_.size(); }
136
+
137
+ private:
138
+ std::vector<uint8_t> data_;
139
+ };
140
+
141
+ } // namespace test
@@ -0,0 +1,118 @@
1
+ /*
2
+ * test_helpers.hpp - Shared test utilities for native C++ tests
3
+ */
4
+
5
+ #pragma once
6
+
7
+ #include <array>
8
+ #include <cstdint>
9
+ #include <cstring>
10
+ #include <vector>
11
+ #include <stdexcept>
12
+ #include "cpu6502.hpp"
13
+
14
+ namespace test {
15
+
16
+ /**
17
+ * FlatMemory - Simple 64KB memory for CPU testing
18
+ *
19
+ * Provides read/write callbacks compatible with CPU6502 constructor.
20
+ * Includes helpers for loading programs, setting vectors, etc.
21
+ */
22
+ class FlatMemory {
23
+ public:
24
+ FlatMemory() { mem_.fill(0); }
25
+
26
+ uint8_t read(uint16_t addr) const { return mem_[addr]; }
27
+ void write(uint16_t addr, uint8_t val) { mem_[addr] = val; }
28
+
29
+ // Set RESET vector ($FFFC-$FFFD)
30
+ void setResetVector(uint16_t addr) {
31
+ mem_[0xFFFC] = addr & 0xFF;
32
+ mem_[0xFFFD] = (addr >> 8) & 0xFF;
33
+ }
34
+
35
+ // Set IRQ/BRK vector ($FFFE-$FFFF)
36
+ void setIRQVector(uint16_t addr) {
37
+ mem_[0xFFFE] = addr & 0xFF;
38
+ mem_[0xFFFF] = (addr >> 8) & 0xFF;
39
+ }
40
+
41
+ // Set NMI vector ($FFFA-$FFFB)
42
+ void setNMIVector(uint16_t addr) {
43
+ mem_[0xFFFA] = addr & 0xFF;
44
+ mem_[0xFFFB] = (addr >> 8) & 0xFF;
45
+ }
46
+
47
+ // Load a byte sequence at a given address
48
+ void loadProgram(uint16_t addr, const std::vector<uint8_t>& bytes) {
49
+ for (size_t i = 0; i < bytes.size(); i++) {
50
+ mem_[static_cast<uint16_t>(addr + i)] = bytes[i];
51
+ }
52
+ }
53
+
54
+ // Load raw byte array
55
+ void loadProgram(uint16_t addr, const uint8_t* data, size_t len) {
56
+ for (size_t i = 0; i < len; i++) {
57
+ mem_[static_cast<uint16_t>(addr + i)] = data[i];
58
+ }
59
+ }
60
+
61
+ // Direct array access
62
+ uint8_t& operator[](uint16_t addr) { return mem_[addr]; }
63
+ const uint8_t& operator[](uint16_t addr) const { return mem_[addr]; }
64
+
65
+ // Clear all memory
66
+ void clear() { mem_.fill(0); }
67
+
68
+ private:
69
+ std::array<uint8_t, 65536> mem_;
70
+ };
71
+
72
+ /**
73
+ * Execute exactly N instructions on the CPU
74
+ */
75
+ inline void runInstructions(a2e::CPU6502& cpu, int count) {
76
+ for (int i = 0; i < count; i++) {
77
+ cpu.executeInstruction();
78
+ }
79
+ }
80
+
81
+ /**
82
+ * Run until PC reaches target address, with a maximum instruction limit.
83
+ * Returns true if target was reached, false if limit was hit.
84
+ */
85
+ inline bool runUntilPC(a2e::CPU6502& cpu, uint16_t target, int maxInstructions = 100000) {
86
+ for (int i = 0; i < maxInstructions; i++) {
87
+ if (cpu.getPC() == target) return true;
88
+ cpu.executeInstruction();
89
+ }
90
+ return cpu.getPC() == target;
91
+ }
92
+
93
+ /**
94
+ * Helper: create a CPU with FlatMemory and reset it to a given PC
95
+ */
96
+ struct CPUTestFixture {
97
+ FlatMemory mem;
98
+ std::unique_ptr<a2e::CPU6502> cpu;
99
+
100
+ CPUTestFixture(uint16_t startPC = 0x0400,
101
+ a2e::CPUVariant variant = a2e::CPUVariant::CMOS_65C02) {
102
+ mem.setResetVector(startPC);
103
+ cpu = std::make_unique<a2e::CPU6502>(
104
+ [this](uint16_t addr) -> uint8_t { return mem.read(addr); },
105
+ [this](uint16_t addr, uint8_t val) { mem.write(addr, val); },
106
+ variant
107
+ );
108
+ cpu->reset();
109
+ }
110
+
111
+ void loadAndReset(uint16_t addr, const std::vector<uint8_t>& program) {
112
+ mem.setResetVector(addr);
113
+ mem.loadProgram(addr, program);
114
+ cpu->reset();
115
+ }
116
+ };
117
+
118
+ } // namespace test