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,343 @@
1
+ /*
2
+ * woz_disk_image.hpp - WOZ 1.0/2.0 bit-accurate disk image format support
3
+ *
4
+ * Written by
5
+ * Mike Daley <michael_daley@icloud.com>
6
+ */
7
+
8
+ #pragma once
9
+
10
+ #include "disk_image.hpp"
11
+ #include <array>
12
+ #include <cstdint>
13
+ #include <memory>
14
+ #include <optional>
15
+ #include <string>
16
+ #include <vector>
17
+
18
+ namespace a2e {
19
+
20
+ /**
21
+ * WozDiskImage - WOZ format disk image implementation
22
+ *
23
+ * WOZ is a bit-accurate disk image format that captures the exact
24
+ * magnetic flux transitions on a 5.25" floppy disk. It supports:
25
+ *
26
+ * - Quarter-track positioning (160 positions for 40 tracks)
27
+ * - Variable track lengths (accurate timing)
28
+ * - Copy-protected disk preservation
29
+ * - Write protection status
30
+ *
31
+ * File format reference: https://applesaucefdc.com/woz/
32
+ *
33
+ * This implementation supports both WOZ 1.0 and WOZ 2.0 formats.
34
+ */
35
+ class WozDiskImage : public DiskImage {
36
+ public:
37
+ WozDiskImage();
38
+ ~WozDiskImage() override = default;
39
+
40
+ // Move operations
41
+ WozDiskImage(WozDiskImage &&) = default;
42
+ WozDiskImage &operator=(WozDiskImage &&) = default;
43
+
44
+ // ===== DiskImage interface implementation =====
45
+
46
+ bool load(const uint8_t *data, size_t size,
47
+ const std::string &filename) override;
48
+ bool isLoaded() const override;
49
+ Format getFormat() const override;
50
+
51
+ // Reset
52
+ void resetState() override;
53
+
54
+ // Head positioning
55
+ void setPhase(int phase, bool on) override;
56
+ int getQuarterTrack() const override;
57
+ int getTrack() const override;
58
+ void setQuarterTrack(int quarter_track) override;
59
+
60
+ // Geometry
61
+ int getTrackCount() const override;
62
+ bool hasData() const override;
63
+
64
+ // Data access
65
+ void advanceBitPosition(uint64_t current_cycles) override;
66
+ uint8_t readNibble() override;
67
+
68
+ // Write operations
69
+ void writeNibble(uint8_t nibble) override;
70
+
71
+ // Bit-level access (for LSS)
72
+ uint8_t readBit() override;
73
+ void writeBit(uint8_t bit) override;
74
+
75
+ bool isWriteProtected() const override;
76
+ bool isModified() const override { return modified_; }
77
+ std::string getFormatName() const override;
78
+ const uint8_t *getSectorData(size_t *size) const override;
79
+ const uint8_t *exportData(size_t *size) override;
80
+
81
+ // ===== Debug Methods =====
82
+ uint8_t getNibbleAt(int track, int position) const override;
83
+ int getTrackNibbleCount(int track) const override;
84
+ size_t getCurrentNibblePosition() const override { return bit_position_ / 8; }
85
+
86
+ // ===== WOZ-specific methods =====
87
+
88
+ /**
89
+ * Create a blank, unformatted WOZ2 disk
90
+ * The disk will have 35 empty tracks filled with sync bytes
91
+ */
92
+ void createBlank();
93
+
94
+ /**
95
+ * Get the disk type from INFO chunk
96
+ * @return 1 = 5.25", 2 = 3.5"
97
+ */
98
+ uint8_t getDiskType() const;
99
+
100
+ /**
101
+ * Get disk type as a human-readable string
102
+ * @return "5.25\"" or "3.5\""
103
+ */
104
+ std::string getDiskTypeString() const;
105
+
106
+ /**
107
+ * Get the optimal bit timing from INFO chunk
108
+ * @return Bit timing in 125ns units (default 32 = 4us)
109
+ */
110
+ uint8_t getOptimalBitTiming() const;
111
+
112
+ private:
113
+ // WOZ file signature constants
114
+ static constexpr uint32_t WOZ1_SIGNATURE = 0x315A4F57; // "WOZ1"
115
+ static constexpr uint32_t WOZ2_SIGNATURE = 0x325A4F57; // "WOZ2"
116
+ static constexpr uint32_t INFO_CHUNK_ID = 0x4F464E49; // "INFO"
117
+ static constexpr uint32_t TMAP_CHUNK_ID = 0x50414D54; // "TMAP"
118
+ static constexpr uint32_t TRKS_CHUNK_ID = 0x534B5254; // "TRKS"
119
+
120
+ // Quarter-track mapping
121
+ static constexpr int QUARTER_TRACK_COUNT = 160;
122
+ static constexpr uint8_t NO_TRACK = 0xFF;
123
+
124
+ // WOZ2 track storage
125
+ static constexpr size_t WOZ2_TRACK_BLOCK_SIZE = 512;
126
+
127
+ #pragma pack(push, 1)
128
+ /**
129
+ * WOZ file header (12 bytes)
130
+ */
131
+ struct WozHeader {
132
+ uint32_t signature; // "WOZ1" or "WOZ2"
133
+ uint8_t high_bits; // 0xFF
134
+ uint8_t lfcrlf[3]; // 0x0A 0x0D 0x0A
135
+ uint32_t crc32; // CRC32 of all data after this field
136
+ };
137
+
138
+ /**
139
+ * Chunk header (8 bytes)
140
+ */
141
+ struct ChunkHeader {
142
+ uint32_t chunk_id; // 4-character chunk identifier
143
+ uint32_t size; // Size of chunk data (not including header)
144
+ };
145
+
146
+ /**
147
+ * INFO chunk data (60 bytes in WOZ2)
148
+ */
149
+ struct InfoChunk {
150
+ uint8_t version; // INFO chunk version (1 or 2)
151
+ uint8_t disk_type; // 1 = 5.25", 2 = 3.5"
152
+ uint8_t write_protected; // 1 = write protected
153
+ uint8_t synchronized; // 1 = tracks are synchronized
154
+ uint8_t cleaned; // 1 = MC3470 fake bits removed
155
+ char creator[32]; // Creator software name
156
+ uint8_t disk_sides; // 1 or 2
157
+ uint8_t boot_sector_format; // 0=unknown, 1=16-sector, 2=13-sector, 3=both
158
+ uint8_t optimal_bit_timing; // 125ns units (default 32 = 4us)
159
+ uint16_t compatible_hardware; // Bit field of compatible hardware
160
+ uint16_t required_ram; // Minimum RAM in KB
161
+ uint16_t largest_track; // Block count of largest track
162
+ uint8_t reserved[10]; // Padding to 60 bytes
163
+ };
164
+
165
+ /**
166
+ * WOZ2 TRKS chunk entry (8 bytes per track)
167
+ */
168
+ struct Woz2TrackEntry {
169
+ uint16_t starting_block; // Starting 512-byte block
170
+ uint16_t block_count; // Number of 512-byte blocks
171
+ uint32_t bit_count; // Number of valid bits in track
172
+ };
173
+ #pragma pack(pop)
174
+
175
+ /**
176
+ * Internal track data storage
177
+ */
178
+ struct TrackData {
179
+ std::vector<uint8_t> bits; // Raw bit data
180
+ uint32_t bit_count = 0; // Number of valid bits
181
+ bool valid = false; // Track has data
182
+ };
183
+
184
+ // Loaded file info
185
+ Format format_ = Format::Unknown;
186
+ bool loaded_ = false;
187
+ bool modified_ = false;
188
+
189
+ // Export buffer for saving
190
+ mutable std::vector<uint8_t> export_buffer_;
191
+
192
+ // Decoded sector data cache (for file explorer support)
193
+ mutable std::vector<uint8_t> decoded_sectors_;
194
+ mutable bool sectors_decoded_ = false;
195
+
196
+ // INFO chunk data
197
+ InfoChunk info_{};
198
+
199
+ // TMAP: quarter-track to track index mapping
200
+ std::array<uint8_t, QUARTER_TRACK_COUNT> tmap_{};
201
+
202
+ // Track data storage (indexed by TMAP values, not quarter-track)
203
+ std::vector<TrackData> tracks_;
204
+
205
+ // ===== Head positioning state =====
206
+ uint8_t phase_states_ = 0; // Bit field for phase magnet states (bits 0-3)
207
+ int quarter_track_ = 0; // Current head position (0-159)
208
+ int current_phase_ = 0; // Current phase where head is settled (for stepper)
209
+
210
+ // ===== Bit position =====
211
+ uint32_t bit_position_ = 0; // Current bit position within track
212
+
213
+ // Cycle count for disk rotation timing
214
+ uint64_t last_cycle_count_ = 0;
215
+
216
+ // ===== Internal methods =====
217
+
218
+ /**
219
+ * Reset all state to unloaded
220
+ */
221
+ void reset();
222
+
223
+ /**
224
+ * Parse INFO chunk
225
+ * @param data Chunk data
226
+ * @param size Chunk size
227
+ * @return true on success
228
+ */
229
+ bool parseInfoChunk(const uint8_t *data, uint32_t size);
230
+
231
+ /**
232
+ * Parse TMAP chunk
233
+ * @param data Chunk data
234
+ * @param size Chunk size
235
+ * @return true on success
236
+ */
237
+ bool parseTmapChunk(const uint8_t *data, uint32_t size);
238
+
239
+ /**
240
+ * Parse TRKS chunk for WOZ1 format
241
+ * @param data Chunk data
242
+ * @param size Chunk size
243
+ * @return true on success
244
+ */
245
+ bool parseTrksChunkWoz1(const uint8_t *data, uint32_t size);
246
+
247
+ /**
248
+ * Parse TRKS chunk for WOZ2 format
249
+ * @param file_data Full file data
250
+ * @param file_size File size
251
+ * @param trks_data TRKS chunk data
252
+ * @param trks_size TRKS chunk size
253
+ * @return true on success
254
+ */
255
+ bool parseTrksChunkWoz2(const uint8_t *file_data, size_t file_size,
256
+ const uint8_t *trks_data, uint32_t trks_size);
257
+
258
+ /**
259
+ * Get track data at current head position
260
+ * @return Pointer to track data, or nullptr if no data
261
+ */
262
+ const TrackData *getCurrentTrackData() const;
263
+
264
+ /**
265
+ * Read a raw bit from the disk at current position (internal helper)
266
+ * @return 0 or 1
267
+ */
268
+ uint8_t readBitInternal() const;
269
+
270
+ /**
271
+ * Update head position based on phase magnet states
272
+ * Called when a phase is turned OFF to check if stepping should occur
273
+ * Uses 4-phase stepper motor physics matching real hardware
274
+ */
275
+ void updateHeadPosition();
276
+
277
+ /**
278
+ * Get mutable track data at current head position
279
+ * @return Pointer to track data, or nullptr if no data
280
+ */
281
+ TrackData *getMutableCurrentTrackData();
282
+
283
+ /**
284
+ * Write a single bit to the disk at current position
285
+ * @param bit The bit value (0 or 1)
286
+ */
287
+ void writeBitInternal(uint8_t bit);
288
+
289
+ // ===== Sector Decoding (for file explorer support) =====
290
+
291
+ /**
292
+ * Decode all sectors from the WOZ bit stream.
293
+ * This converts the flux-level data back into standard sector data,
294
+ * enabling file explorer support for standard DOS 3.3 and ProDOS disks.
295
+ * Copy-protected disks may not decode successfully.
296
+ *
297
+ * @return true if decoding succeeded, false otherwise
298
+ */
299
+ bool decodeSectors() const;
300
+
301
+ /**
302
+ * Read nibbles from a track's bit stream.
303
+ * Handles the bit-to-nibble conversion including sync byte detection.
304
+ *
305
+ * @param track_index Index of track in tracks_ array
306
+ * @return Vector of nibbles read from the track
307
+ */
308
+ std::vector<uint8_t> readTrackNibbles(size_t track_index) const;
309
+
310
+ /**
311
+ * Find and decode sectors from a nibble stream.
312
+ * Searches for address and data field markers, decodes the 6-and-2 data.
313
+ *
314
+ * @param nibbles Nibble stream from a track
315
+ * @param track Expected track number
316
+ * @param sectors Output: 16 decoded sectors (256 bytes each)
317
+ * @return Number of sectors successfully decoded (0-16)
318
+ */
319
+ int decodeSectorsFromNibbles(const std::vector<uint8_t> &nibbles, int track,
320
+ std::array<std::array<uint8_t, 256>, 16> &sectors) const;
321
+
322
+ /**
323
+ * Decode 4-and-4 encoded byte.
324
+ * Used for address field values (volume, track, sector, checksum).
325
+ *
326
+ * @param odd First nibble (odd bits)
327
+ * @param even Second nibble (even bits)
328
+ * @return Decoded byte value
329
+ */
330
+ static uint8_t decode4and4(uint8_t odd, uint8_t even);
331
+
332
+ /**
333
+ * Decode 6-and-2 encoded sector data.
334
+ * Converts 342 disk nibbles back into 256 data bytes.
335
+ *
336
+ * @param nibbles Pointer to 343 nibbles (342 data + 1 checksum)
337
+ * @param output Output buffer for 256 decoded bytes
338
+ * @return true if checksum valid, false otherwise
339
+ */
340
+ static bool decode6and2(const uint8_t *nibbles, uint8_t *output);
341
+ };
342
+
343
+ } // namespace a2e