badline 0.12.1 → 0.14.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 (565) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +66 -1
  3. data/CLAUDE.md +86 -38
  4. data/CONTRIBUTING.md +4 -3
  5. data/README.md +9 -6
  6. data/doc/library.md +9 -9
  7. data/doc/media.md +2 -1
  8. data/doc/pinned-behaviour.md +134 -105
  9. data/doc/snapshot-format.md +1047 -0
  10. data/doc/snapshots.md +73 -43
  11. data/lib/badline/audio/bare_player.rb +53 -20
  12. data/lib/badline/audio/cli.rb +6 -6
  13. data/lib/badline/audio/jukebox.rb +2 -2
  14. data/lib/badline/audio/machine_player.rb +16 -10
  15. data/lib/badline/{media → audio}/queue.rb +1 -1
  16. data/lib/badline/audio/queued_tune.rb +6 -6
  17. data/lib/badline/audio/renderer.rb +33 -22
  18. data/lib/badline/audio/stereo.rb +1 -1
  19. data/lib/badline/c128/banks.rb +6 -3
  20. data/lib/badline/c128/bus/keyed_state.rb +45 -0
  21. data/lib/badline/c128/bus.rb +35 -20
  22. data/lib/badline/c128/color_lines.rb +2 -2
  23. data/lib/badline/c128/cpu.rb +1 -1
  24. data/lib/badline/c128/drives.rb +14 -34
  25. data/lib/badline/c128/fast_serial.rb +5 -5
  26. data/lib/badline/c128/keyed_modules.rb +117 -0
  27. data/lib/badline/c128/keyed_state.rb +91 -0
  28. data/lib/badline/c128/keys.rb +9 -0
  29. data/lib/badline/c128/mmu.rb +9 -8
  30. data/lib/badline/c128/mmu_pages.rb +21 -1
  31. data/lib/badline/c128/modes.rb +21 -11
  32. data/lib/badline/c128/z80.rb +2 -2
  33. data/lib/badline/c128/z80_turns.rb +1 -1
  34. data/lib/badline/c128.rb +21 -16
  35. data/lib/badline/c64/attachments.rb +42 -0
  36. data/lib/badline/c64/banked_ram/banking.rb +32 -0
  37. data/lib/badline/c64/banked_ram/plus256k.rb +64 -0
  38. data/lib/badline/c64/banked_ram/plus60k.rb +47 -0
  39. data/lib/badline/c64/banked_ram/unexpanded.rb +30 -0
  40. data/lib/badline/c64/banked_ram.rb +23 -0
  41. data/lib/badline/c64/bus/debug_register.rb +32 -0
  42. data/lib/badline/c64/bus/extra_sids.rb +32 -0
  43. data/lib/badline/c64/bus/fittings.rb +40 -0
  44. data/lib/badline/c64/bus/keyed_state.rb +41 -0
  45. data/lib/badline/c64/bus/pla.rb +149 -0
  46. data/lib/badline/c64/bus/roms.rb +41 -0
  47. data/lib/badline/c64/bus/sid_slots.rb +44 -0
  48. data/lib/badline/c64/bus.rb +219 -0
  49. data/lib/badline/c64/cartridge/action_replay.rb +115 -0
  50. data/lib/badline/c64/cartridge/atomic_power.rb +26 -0
  51. data/lib/badline/c64/cartridge/bank.rb +83 -0
  52. data/lib/badline/c64/cartridge/comal80.rb +52 -0
  53. data/lib/badline/c64/cartridge/dinamic.rb +36 -0
  54. data/lib/badline/c64/cartridge/easy_flash.rb +132 -0
  55. data/lib/badline/c64/cartridge/epyx_fastload.rb +73 -0
  56. data/lib/badline/c64/cartridge/final_cartridge3.rb +79 -0
  57. data/lib/badline/c64/cartridge/flash.rb +279 -0
  58. data/lib/badline/c64/cartridge/freezer.rb +35 -0
  59. data/lib/badline/c64/cartridge/fun_play.rb +40 -0
  60. data/lib/badline/c64/cartridge/g_mod2.rb +64 -0
  61. data/lib/badline/c64/cartridge/game_system.rb +42 -0
  62. data/lib/badline/c64/cartridge/geo_ram.rb +65 -0
  63. data/lib/badline/c64/cartridge/kcs_power.rb +69 -0
  64. data/lib/badline/c64/cartridge/keyed_state.rb +393 -0
  65. data/lib/badline/c64/cartridge/mach5.rb +34 -0
  66. data/lib/badline/c64/cartridge/magic_desk.rb +32 -0
  67. data/lib/badline/c64/cartridge/ocean.rb +37 -0
  68. data/lib/badline/c64/cartridge/pagefox.rb +60 -0
  69. data/lib/badline/c64/cartridge/retro_replay.rb +278 -0
  70. data/lib/badline/c64/cartridge/rex_utility.rb +31 -0
  71. data/lib/badline/c64/cartridge/rgcd.rb +50 -0
  72. data/lib/badline/c64/cartridge/simons_basic.rb +37 -0
  73. data/lib/badline/c64/cartridge/standard.rb +17 -0
  74. data/lib/badline/c64/cartridge/super_games.rb +40 -0
  75. data/lib/badline/c64/cartridge/westermann.rb +32 -0
  76. data/lib/badline/c64/cartridge/zaxxon.rb +52 -0
  77. data/lib/badline/c64/cartridge.rb +232 -0
  78. data/lib/badline/c64/color_memory.rb +27 -0
  79. data/lib/badline/{computer → c64}/common.rb +2 -2
  80. data/lib/badline/{computer → c64}/kernal_traps.rb +39 -36
  81. data/lib/badline/c64/keyed_modules.rb +111 -0
  82. data/lib/badline/c64/keyed_state.rb +68 -0
  83. data/lib/badline/c64/reu/dma.rb +271 -0
  84. data/lib/badline/c64/reu/keyed_state.rb +140 -0
  85. data/lib/badline/c64/reu/ram.rb +94 -0
  86. data/lib/badline/c64/reu/trigger.rb +34 -0
  87. data/lib/badline/c64/reu.rb +289 -0
  88. data/lib/badline/c64/true_drives.rb +120 -0
  89. data/lib/badline/{computer.rb → c64.rb} +24 -15
  90. data/lib/badline/chips/cia/fast_forward.rb +62 -0
  91. data/lib/badline/chips/cia/interrupt_register.rb +128 -0
  92. data/lib/badline/chips/cia/keyed_parts.rb +213 -0
  93. data/lib/badline/chips/cia/serial.rb +246 -0
  94. data/lib/badline/chips/cia/time_of_day.rb +210 -0
  95. data/lib/badline/chips/cia/timer.rb +188 -0
  96. data/lib/badline/chips/cia.rb +281 -0
  97. data/lib/badline/chips/cpu/addressing.rb +133 -0
  98. data/lib/badline/chips/cpu/boundary.rb +56 -0
  99. data/lib/badline/chips/cpu/core.rb +183 -0
  100. data/lib/badline/chips/cpu/instruction.rb +338 -0
  101. data/lib/badline/chips/cpu/instruction_set/arithmetic.rb +118 -0
  102. data/lib/badline/chips/cpu/instruction_set/bitwise.rb +130 -0
  103. data/lib/badline/chips/cpu/instruction_set/branch.rb +83 -0
  104. data/lib/badline/chips/cpu/instruction_set/flag.rb +67 -0
  105. data/lib/badline/chips/cpu/instruction_set/illegal.rb +274 -0
  106. data/lib/badline/chips/cpu/instruction_set/inc_dec.rb +73 -0
  107. data/lib/badline/chips/cpu/instruction_set/stack.rb +56 -0
  108. data/lib/badline/chips/cpu/instruction_set/transfer.rb +141 -0
  109. data/lib/badline/chips/cpu/instruction_set.rb +59 -0
  110. data/lib/badline/chips/cpu/interrupts.rb +108 -0
  111. data/lib/badline/chips/cpu/keyed_state.rb +177 -0
  112. data/lib/badline/chips/cpu/microcode.rb +195 -0
  113. data/lib/badline/chips/cpu/operations.rb +90 -0
  114. data/lib/badline/chips/cpu/port.rb +133 -0
  115. data/lib/badline/chips/cpu/stack_operations.rb +84 -0
  116. data/lib/badline/chips/cpu/traps.rb +23 -0
  117. data/lib/badline/chips/cpu.rb +17 -0
  118. data/lib/badline/chips/sid/catch_up.rb +122 -0
  119. data/lib/badline/chips/sid/decimator.rb +68 -0
  120. data/lib/badline/chips/sid/envelope.rb +236 -0
  121. data/lib/badline/chips/sid/filter.rb +227 -0
  122. data/lib/badline/chips/sid/keyed_parts.rb +286 -0
  123. data/lib/badline/chips/sid/voice.rb +69 -0
  124. data/lib/badline/chips/sid/waveform/combined.rb +128 -0
  125. data/lib/badline/chips/sid/waveform/fast_forward.rb +107 -0
  126. data/lib/badline/chips/sid/waveform/noise_writeback.rb +124 -0
  127. data/lib/badline/chips/sid/waveform.rb +305 -0
  128. data/lib/badline/chips/sid.rb +264 -0
  129. data/lib/badline/chips/ted/beam.rb +119 -0
  130. data/lib/badline/chips/ted/timers.rb +66 -0
  131. data/lib/badline/chips/ted.rb +143 -0
  132. data/lib/badline/chips/tia.rb +49 -0
  133. data/lib/badline/chips/vdc/keyed_parts.rb +105 -0
  134. data/lib/badline/{c128 → chips}/vdc/memory.rb +1 -1
  135. data/lib/badline/{c128 → chips}/vdc/painter.rb +1 -13
  136. data/lib/badline/{c128 → chips}/vdc/raster.rb +1 -17
  137. data/lib/badline/{c128 → chips}/vdc/window.rb +1 -10
  138. data/lib/badline/{c128 → chips}/vdc.rb +6 -31
  139. data/lib/badline/chips/via/control_lines.rb +109 -0
  140. data/lib/badline/chips/via/fast_forward.rb +55 -0
  141. data/lib/badline/chips/via/interrupt_register.rb +60 -0
  142. data/lib/badline/chips/via/keyed_parts.rb +163 -0
  143. data/lib/badline/chips/via/shift_register.rb +168 -0
  144. data/lib/badline/chips/via/timer1.rb +120 -0
  145. data/lib/badline/chips/via/timer2.rb +117 -0
  146. data/lib/badline/chips/via.rb +267 -0
  147. data/lib/badline/chips/vic/bank.rb +135 -0
  148. data/lib/badline/chips/vic/border_mask.rb +78 -0
  149. data/lib/badline/chips/vic/collisions.rb +106 -0
  150. data/lib/badline/chips/vic/color_patches.rb +38 -0
  151. data/lib/badline/chips/vic/display_state.rb +168 -0
  152. data/lib/badline/chips/vic/graphics_mode.rb +166 -0
  153. data/lib/badline/chips/vic/graphics_shifter.rb +183 -0
  154. data/lib/badline/chips/vic/keyed_parts.rb +228 -0
  155. data/lib/badline/chips/vic/keyed_sprites.rb +225 -0
  156. data/lib/badline/chips/vic/keyed_state.rb +102 -0
  157. data/lib/badline/chips/vic/register_log.rb +65 -0
  158. data/lib/badline/chips/vic/registers.rb +188 -0
  159. data/lib/badline/chips/vic/sequencer.rb +275 -0
  160. data/lib/badline/chips/vic/sequencer_output.rb +118 -0
  161. data/lib/badline/chips/vic/sprite/internal_bus.rb +59 -0
  162. data/lib/badline/chips/vic/sprite/shifter.rb +148 -0
  163. data/lib/badline/chips/vic/sprite/timing.rb +58 -0
  164. data/lib/badline/chips/vic/sprite.rb +337 -0
  165. data/lib/badline/chips/vic/sprites.rb +281 -0
  166. data/lib/badline/chips/vic.rb +787 -0
  167. data/lib/badline/chips/vic_i/keyed_parts.rb +142 -0
  168. data/lib/badline/{vic20/vic → chips/vic_i}/painter.rb +2 -30
  169. data/lib/badline/chips/vic_i/sound/output.rb +85 -0
  170. data/lib/badline/chips/vic_i/sound.rb +227 -0
  171. data/lib/badline/{vic20/vic → chips/vic_i}/text_window.rb +2 -2
  172. data/lib/badline/{vic20/vic.rb → chips/vic_i.rb} +6 -6
  173. data/lib/badline/chips/z80/arithmetic.rb +119 -0
  174. data/lib/badline/chips/z80/bit_page.rb +57 -0
  175. data/lib/badline/chips/z80/block_io.rb +60 -0
  176. data/lib/badline/chips/z80/block_transfers.rb +61 -0
  177. data/lib/badline/chips/z80/bus_cycles.rb +113 -0
  178. data/lib/badline/chips/z80/core.rb +115 -0
  179. data/lib/badline/chips/z80/exchanges.rb +52 -0
  180. data/lib/badline/chips/z80/extended_page.rb +91 -0
  181. data/lib/badline/chips/z80/flags.rb +39 -0
  182. data/lib/badline/chips/z80/interrupt_acceptance.rb +48 -0
  183. data/lib/badline/chips/z80/jumps.rb +84 -0
  184. data/lib/badline/chips/z80/keyed_state.rb +73 -0
  185. data/lib/badline/chips/z80/loads.rb +100 -0
  186. data/lib/badline/chips/z80/main_page.rb +136 -0
  187. data/lib/badline/chips/z80/registers.rb +122 -0
  188. data/lib/badline/chips/z80/shifts.rb +66 -0
  189. data/lib/badline/chips/z80.rb +13 -0
  190. data/lib/badline/chips.rb +10 -0
  191. data/lib/badline/core.rb +13 -32
  192. data/lib/badline/drives/datasette/keyed_state.rb +38 -0
  193. data/lib/badline/drives/datasette.rb +173 -0
  194. data/lib/badline/drives/engine/core.rb +152 -0
  195. data/lib/badline/drives/engine/fast_serial.rb +75 -0
  196. data/lib/badline/drives/engine/idle.rb +127 -0
  197. data/lib/badline/drives/engine/idle_cpu.rb +30 -0
  198. data/lib/badline/drives/engine/orbit.rb +81 -0
  199. data/lib/badline/drives/engine/serial_lines.rb +72 -0
  200. data/lib/badline/drives/engine/sleep.rb +114 -0
  201. data/lib/badline/drives/engine/vias.rb +86 -0
  202. data/lib/badline/drives/engine/watch.rb +152 -0
  203. data/lib/badline/drives/engine.rb +11 -0
  204. data/lib/badline/drives/model1541/bus.rb +75 -0
  205. data/lib/badline/drives/model1541/byte_ready.rb +50 -0
  206. data/lib/badline/drives/model1541/cpu.rb +13 -0
  207. data/lib/badline/drives/model1541/disk.rb +318 -0
  208. data/lib/badline/drives/model1541/disk_via.rb +38 -0
  209. data/lib/badline/drives/model1541/gcr.rb +59 -0
  210. data/lib/badline/drives/model1541/keyed_state.rb +251 -0
  211. data/lib/badline/drives/model1541/mechanism.rb +313 -0
  212. data/lib/badline/drives/model1541/rotation.rb +123 -0
  213. data/lib/badline/drives/model1541/sector_reader.rb +78 -0
  214. data/lib/badline/drives/model1541/serial_port.rb +20 -0
  215. data/lib/badline/drives/model1541/track.rb +100 -0
  216. data/lib/badline/drives/model1541.rb +80 -0
  217. data/lib/badline/drives/model1571/bus.rb +100 -0
  218. data/lib/badline/drives/model1571/cpu.rb +13 -0
  219. data/lib/badline/drives/model1571/keyed_state.rb +108 -0
  220. data/lib/badline/drives/model1571/serial_port.rb +39 -0
  221. data/lib/badline/drives/model1571/serial_via.rb +27 -0
  222. data/lib/badline/drives/model1571/wd1770.rb +108 -0
  223. data/lib/badline/drives/model1571.rb +141 -0
  224. data/lib/badline/drives/model1581/bus.rb +126 -0
  225. data/lib/badline/drives/model1581/cpu.rb +13 -0
  226. data/lib/badline/drives/model1581/disk.rb +133 -0
  227. data/lib/badline/drives/model1581/keyed_state.rb +280 -0
  228. data/lib/badline/drives/model1581/mechanism.rb +98 -0
  229. data/lib/badline/drives/model1581/serial_port.rb +54 -0
  230. data/lib/badline/drives/model1581/slot.rb +61 -0
  231. data/lib/badline/drives/model1581/track.rb +102 -0
  232. data/lib/badline/drives/model1581/track_bytes.rb +77 -0
  233. data/lib/badline/drives/model1581/wd1772/registers.rb +91 -0
  234. data/lib/badline/drives/model1581/wd1772/sector_writes.rb +75 -0
  235. data/lib/badline/drives/model1581/wd1772/sectors.rb +152 -0
  236. data/lib/badline/drives/model1581/wd1772/stepping.rb +111 -0
  237. data/lib/badline/drives/model1581/wd1772/tracks.rb +127 -0
  238. data/lib/badline/drives/model1581/wd1772/turn.rb +75 -0
  239. data/lib/badline/drives/model1581/wd1772.rb +216 -0
  240. data/lib/badline/drives/model1581.rb +206 -0
  241. data/lib/badline/drives/serial_bus.rb +161 -0
  242. data/lib/badline/drives.rb +8 -0
  243. data/lib/badline/frontend/app.rb +3 -3
  244. data/lib/badline/frontend/boot.rb +4 -4
  245. data/lib/badline/frontend/controls.rb +19 -3
  246. data/lib/badline/frontend/drive_led.rb +44 -21
  247. data/lib/badline/frontend/machine_window.rb +3 -1
  248. data/lib/badline/frontend/menu/menu_dialogs.rb +2 -2
  249. data/lib/badline/frontend/player/filter_view.rb +1 -1
  250. data/lib/badline/frontend/player/player_controller.rb +2 -2
  251. data/lib/badline/frontend/player/player_screen.rb +1 -0
  252. data/lib/badline/frontend/player/theme.rb +7 -7
  253. data/lib/badline/frontend/snapshots.rb +12 -1
  254. data/lib/badline/frontend/timeline.rb +30 -12
  255. data/lib/badline/input/control_ports.rb +78 -0
  256. data/lib/badline/input/joystick.rb +27 -0
  257. data/lib/badline/input/keyboard.rb +114 -0
  258. data/lib/badline/input/matrix_typist.rb +98 -0
  259. data/lib/badline/input.rb +4 -0
  260. data/lib/badline/kernal_trap/chrout.rb +45 -0
  261. data/lib/badline/kernal_trap/{drive → dos}/block_commands.rb +1 -1
  262. data/lib/badline/kernal_trap/dos/channel.rb +156 -0
  263. data/lib/badline/kernal_trap/{drive → dos}/channels.rb +1 -26
  264. data/lib/badline/kernal_trap/{drive → dos}/memory.rb +1 -10
  265. data/lib/badline/kernal_trap/{drive → dos}/parameters.rb +1 -1
  266. data/lib/badline/kernal_trap/{drive → dos}/status.rb +1 -9
  267. data/lib/badline/kernal_trap/{drive → dos}/write_file.rb +1 -19
  268. data/lib/badline/kernal_trap/{drive → dos}/writes.rb +1 -1
  269. data/lib/badline/kernal_trap/{drive.rb → dos.rb} +9 -27
  270. data/lib/badline/kernal_trap/file_routine.rb +4 -4
  271. data/lib/badline/kernal_trap/keyed_state.rb +150 -0
  272. data/lib/badline/kernal_trap/layout.rb +75 -11
  273. data/lib/badline/kernal_trap/load.rb +19 -21
  274. data/lib/badline/kernal_trap/routine.rb +12 -0
  275. data/lib/badline/kernal_trap/save.rb +12 -22
  276. data/lib/badline/kernal_trap/serial.rb +8 -24
  277. data/lib/badline/kernal_trap.rb +3 -2
  278. data/lib/badline/machine/clocking.rb +1 -1
  279. data/lib/badline/machine/keyboard_buffer.rb +49 -0
  280. data/lib/badline/machine/settling.rb +29 -0
  281. data/lib/badline/machine.rb +10 -7
  282. data/lib/badline/media/boot_disk.rb +14 -2
  283. data/lib/badline/media/extensions.rb +10 -0
  284. data/lib/badline/media/true_drive.rb +1 -1
  285. data/lib/badline/media/tune.rb +2 -2
  286. data/lib/badline/media.rb +3 -3
  287. data/lib/badline/memory.rb +3 -6
  288. data/lib/badline/model.rb +1 -1
  289. data/lib/badline/options/family.rb +5 -2
  290. data/lib/badline/options/help.rb +42 -16
  291. data/lib/badline/options/table.rb +2 -0
  292. data/lib/badline/options/validation.rb +2 -0
  293. data/lib/badline/options.rb +6 -1
  294. data/lib/badline/plus4/acia.rb +45 -0
  295. data/lib/badline/plus4/bus.rb +312 -0
  296. data/lib/badline/plus4/cpu.rb +10 -0
  297. data/lib/badline/plus4/cpu_port.rb +93 -0
  298. data/lib/badline/plus4/keyboard_port.rb +51 -0
  299. data/lib/badline/plus4/model.rb +38 -0
  300. data/lib/badline/plus4/user_port.rb +31 -0
  301. data/lib/badline/plus4.rb +164 -0
  302. data/lib/badline/roms/README +44 -6
  303. data/lib/badline/roms/plus4/basic.rom +0 -0
  304. data/lib/badline/roms/plus4/function-high.rom +0 -0
  305. data/lib/badline/roms/plus4/function-low.rom +0 -0
  306. data/lib/badline/roms/plus4/kernal-ntsc.rom +0 -0
  307. data/lib/badline/roms/plus4/kernal-pal.rom +0 -0
  308. data/lib/badline/sdl.rb +1 -0
  309. data/lib/badline/{storage/sid_file.rb → sid_tune/file.rb} +3 -3
  310. data/lib/badline/{storage → sid_tune}/hvsc.rb +7 -7
  311. data/lib/badline/{storage → sid_tune}/song_lengths.rb +2 -2
  312. data/lib/badline/{storage → sid_tune}/stil/credit.rb +1 -1
  313. data/lib/badline/{storage → sid_tune}/stil.rb +3 -3
  314. data/lib/badline/sid_tune.rb +6 -0
  315. data/lib/badline/snapshot/c128_setup.rb +32 -0
  316. data/lib/badline/snapshot/c64_setup.rb +83 -0
  317. data/lib/badline/snapshot/container.rb +4 -0
  318. data/lib/badline/snapshot/field_doc/markdown.rb +112 -0
  319. data/lib/badline/snapshot/field_doc.rb +109 -0
  320. data/lib/badline/snapshot/image.rb +132 -46
  321. data/lib/badline/snapshot/keyed/cursor.rb +40 -0
  322. data/lib/badline/snapshot/keyed/modules.rb +66 -0
  323. data/lib/badline/snapshot/keyed/reader.rb +188 -0
  324. data/lib/badline/snapshot/keyed/recorder.rb +88 -0
  325. data/lib/badline/snapshot/keyed/report.rb +26 -0
  326. data/lib/badline/snapshot/keyed/writer.rb +55 -0
  327. data/lib/badline/snapshot/keyed.rb +64 -0
  328. data/lib/badline/snapshot/machine_state.rb +17 -64
  329. data/lib/badline/snapshot/storage_setup.rb +63 -0
  330. data/lib/badline/snapshot/vic20_setup.rb +28 -0
  331. data/lib/badline/snapshot/vice/c64_mem.rb +1 -1
  332. data/lib/badline/snapshot/vice/restore.rb +1 -1
  333. data/lib/badline/snapshot/vice/reu1764.rb +3 -3
  334. data/lib/badline/snapshot/vice/reu_fields.rb +40 -0
  335. data/lib/badline/snapshot/vice.rb +13 -35
  336. data/lib/badline/snapshot.rb +44 -22
  337. data/lib/badline/storage/crt_file.rb +3 -1
  338. data/lib/badline/storage/d64_image.rb +0 -2
  339. data/lib/badline/storage/d71_image.rb +0 -2
  340. data/lib/badline/storage/d81_image.rb +0 -2
  341. data/lib/badline/storage/disk_image.rb +3 -6
  342. data/lib/badline/storage/g64_image.rb +10 -5
  343. data/lib/badline/storage/host_directory.rb +0 -5
  344. data/lib/badline/storage/t64.rb +2 -8
  345. data/lib/badline/storage/tap.rb +43 -15
  346. data/lib/badline/storage/unavailable.rb +1 -1
  347. data/lib/badline/storage.rb +0 -37
  348. data/lib/badline/support/c128_checkpoint.rb +39 -0
  349. data/lib/badline/support/checkpoint.rb +108 -0
  350. data/lib/badline/support/vic20_checkpoint.rb +38 -0
  351. data/lib/badline/support.rb +5 -0
  352. data/lib/badline/version.rb +1 -1
  353. data/lib/badline/vic20/attachments.rb +8 -5
  354. data/lib/badline/vic20/bus/keyed_state.rb +52 -0
  355. data/lib/badline/vic20/bus.rb +2 -25
  356. data/lib/badline/vic20/cpu.rb +1 -1
  357. data/lib/badline/vic20/keyed_modules.rb +73 -0
  358. data/lib/badline/vic20/keyed_state.rb +45 -0
  359. data/lib/badline/vic20/port_wiring.rb +4 -4
  360. data/lib/badline/vic20/user_via_ports.rb +2 -2
  361. data/lib/badline/vic20.rb +22 -17
  362. data/lib/badline.rb +1 -0
  363. metadata +271 -228
  364. data/lib/badline/address_bus/extra_sids.rb +0 -30
  365. data/lib/badline/address_bus/fittings.rb +0 -38
  366. data/lib/badline/address_bus/pla.rb +0 -147
  367. data/lib/badline/address_bus/roms.rb +0 -39
  368. data/lib/badline/address_bus/saved_state.rb +0 -66
  369. data/lib/badline/address_bus/sid_slots.rb +0 -42
  370. data/lib/badline/address_bus.rb +0 -216
  371. data/lib/badline/c128/bus_state.rb +0 -53
  372. data/lib/badline/c128/saved_state.rb +0 -143
  373. data/lib/badline/cartridge/action_replay.rb +0 -126
  374. data/lib/badline/cartridge/atomic_power.rb +0 -24
  375. data/lib/badline/cartridge/bank.rb +0 -81
  376. data/lib/badline/cartridge/comal80.rb +0 -50
  377. data/lib/badline/cartridge/dinamic.rb +0 -34
  378. data/lib/badline/cartridge/easy_flash.rb +0 -145
  379. data/lib/badline/cartridge/epyx_fastload.rb +0 -79
  380. data/lib/badline/cartridge/final_cartridge3.rb +0 -89
  381. data/lib/badline/cartridge/flash.rb +0 -306
  382. data/lib/badline/cartridge/freezer.rb +0 -33
  383. data/lib/badline/cartridge/fun_play.rb +0 -38
  384. data/lib/badline/cartridge/g_mod2.rb +0 -74
  385. data/lib/badline/cartridge/game_system.rb +0 -40
  386. data/lib/badline/cartridge/geo_ram.rb +0 -79
  387. data/lib/badline/cartridge/kcs_power.rb +0 -75
  388. data/lib/badline/cartridge/mach5.rb +0 -32
  389. data/lib/badline/cartridge/magic_desk.rb +0 -30
  390. data/lib/badline/cartridge/ocean.rb +0 -35
  391. data/lib/badline/cartridge/pagefox.rb +0 -68
  392. data/lib/badline/cartridge/retro_replay.rb +0 -320
  393. data/lib/badline/cartridge/rex_utility.rb +0 -29
  394. data/lib/badline/cartridge/rgcd.rb +0 -58
  395. data/lib/badline/cartridge/simons_basic.rb +0 -35
  396. data/lib/badline/cartridge/standard.rb +0 -15
  397. data/lib/badline/cartridge/super_games.rb +0 -48
  398. data/lib/badline/cartridge/westermann.rb +0 -30
  399. data/lib/badline/cartridge/zaxxon.rb +0 -66
  400. data/lib/badline/cartridge.rb +0 -303
  401. data/lib/badline/checkpoint.rb +0 -98
  402. data/lib/badline/chrout_trap.rb +0 -43
  403. data/lib/badline/cia/fast_forward.rb +0 -60
  404. data/lib/badline/cia/interrupt_register.rb +0 -143
  405. data/lib/badline/cia/serial.rb +0 -275
  406. data/lib/badline/cia/state.rb +0 -47
  407. data/lib/badline/cia/timer.rb +0 -206
  408. data/lib/badline/cia.rb +0 -279
  409. data/lib/badline/color_memory.rb +0 -25
  410. data/lib/badline/computer/attachments.rb +0 -88
  411. data/lib/badline/computer/saved_state.rb +0 -117
  412. data/lib/badline/computer/true_drives.rb +0 -69
  413. data/lib/badline/control_ports.rb +0 -76
  414. data/lib/badline/cpu/addressing.rb +0 -131
  415. data/lib/badline/cpu/boundary.rb +0 -54
  416. data/lib/badline/cpu/core.rb +0 -165
  417. data/lib/badline/cpu/microcode.rb +0 -177
  418. data/lib/badline/cpu/operations.rb +0 -88
  419. data/lib/badline/cpu/saved_state.rb +0 -115
  420. data/lib/badline/cpu/stack_operations.rb +0 -82
  421. data/lib/badline/cpu.rb +0 -10
  422. data/lib/badline/cpu_port.rb +0 -112
  423. data/lib/badline/datasette.rb +0 -161
  424. data/lib/badline/debug_register.rb +0 -28
  425. data/lib/badline/drive/core.rb +0 -147
  426. data/lib/badline/drive/fast_serial.rb +0 -73
  427. data/lib/badline/drive/idle.rb +0 -125
  428. data/lib/badline/drive/idle_cpu.rb +0 -28
  429. data/lib/badline/drive/orbit.rb +0 -79
  430. data/lib/badline/drive/serial_lines.rb +0 -73
  431. data/lib/badline/drive/sleep.rb +0 -112
  432. data/lib/badline/drive/vias.rb +0 -84
  433. data/lib/badline/drive/watch.rb +0 -150
  434. data/lib/badline/drive.rb +0 -11
  435. data/lib/badline/drive1541/bus.rb +0 -86
  436. data/lib/badline/drive1541/byte_ready.rb +0 -48
  437. data/lib/badline/drive1541/cpu.rb +0 -11
  438. data/lib/badline/drive1541/disk.rb +0 -315
  439. data/lib/badline/drive1541/disk_state.rb +0 -86
  440. data/lib/badline/drive1541/disk_via.rb +0 -36
  441. data/lib/badline/drive1541/gcr.rb +0 -57
  442. data/lib/badline/drive1541/mechanism.rb +0 -313
  443. data/lib/badline/drive1541/mechanism_state.rb +0 -68
  444. data/lib/badline/drive1541/rotation.rb +0 -121
  445. data/lib/badline/drive1541/sector_reader.rb +0 -76
  446. data/lib/badline/drive1541/serial_port.rb +0 -18
  447. data/lib/badline/drive1541/track.rb +0 -98
  448. data/lib/badline/drive1541.rb +0 -111
  449. data/lib/badline/drive1571/bus.rb +0 -111
  450. data/lib/badline/drive1571/cpu.rb +0 -11
  451. data/lib/badline/drive1571/serial_port.rb +0 -37
  452. data/lib/badline/drive1571/serial_via.rb +0 -25
  453. data/lib/badline/drive1571/wd1770.rb +0 -120
  454. data/lib/badline/drive1571.rb +0 -175
  455. data/lib/badline/drive1581/bus.rb +0 -137
  456. data/lib/badline/drive1581/cpu.rb +0 -11
  457. data/lib/badline/drive1581/disk.rb +0 -135
  458. data/lib/badline/drive1581/disk_state.rb +0 -87
  459. data/lib/badline/drive1581/mechanism.rb +0 -117
  460. data/lib/badline/drive1581/serial_port.rb +0 -52
  461. data/lib/badline/drive1581/slot.rb +0 -78
  462. data/lib/badline/drive1581/track.rb +0 -100
  463. data/lib/badline/drive1581/track_bytes.rb +0 -75
  464. data/lib/badline/drive1581/wd1772/registers.rb +0 -89
  465. data/lib/badline/drive1581/wd1772/sector_writes.rb +0 -73
  466. data/lib/badline/drive1581/wd1772/sectors.rb +0 -150
  467. data/lib/badline/drive1581/wd1772/state.rb +0 -66
  468. data/lib/badline/drive1581/wd1772/stepping.rb +0 -109
  469. data/lib/badline/drive1581/wd1772/tracks.rb +0 -125
  470. data/lib/badline/drive1581/wd1772/turn.rb +0 -73
  471. data/lib/badline/drive1581/wd1772.rb +0 -216
  472. data/lib/badline/drive1581.rb +0 -233
  473. data/lib/badline/iec_bus.rb +0 -159
  474. data/lib/badline/instruction.rb +0 -334
  475. data/lib/badline/instruction_set/arithmetic.rb +0 -114
  476. data/lib/badline/instruction_set/bitwise.rb +0 -126
  477. data/lib/badline/instruction_set/branch.rb +0 -79
  478. data/lib/badline/instruction_set/flag.rb +0 -63
  479. data/lib/badline/instruction_set/illegal.rb +0 -270
  480. data/lib/badline/instruction_set/inc_dec.rb +0 -69
  481. data/lib/badline/instruction_set/stack.rb +0 -52
  482. data/lib/badline/instruction_set/transfer.rb +0 -137
  483. data/lib/badline/instruction_set.rb +0 -55
  484. data/lib/badline/interrupts.rb +0 -104
  485. data/lib/badline/joystick.rb +0 -25
  486. data/lib/badline/kernal_trap/channel.rb +0 -181
  487. data/lib/badline/keyboard.rb +0 -112
  488. data/lib/badline/keyboard_buffer.rb +0 -33
  489. data/lib/badline/ram_expansion/banking.rb +0 -30
  490. data/lib/badline/ram_expansion/plus256k.rb +0 -54
  491. data/lib/badline/ram_expansion/plus60k.rb +0 -43
  492. data/lib/badline/ram_expansion/unexpanded.rb +0 -28
  493. data/lib/badline/ram_expansion.rb +0 -21
  494. data/lib/badline/reu/dma/saved_state.rb +0 -50
  495. data/lib/badline/reu/dma.rb +0 -273
  496. data/lib/badline/reu/ram.rb +0 -107
  497. data/lib/badline/reu/saved_state.rb +0 -80
  498. data/lib/badline/reu/trigger.rb +0 -32
  499. data/lib/badline/reu.rb +0 -289
  500. data/lib/badline/sid/catch_up.rb +0 -120
  501. data/lib/badline/sid/decimator.rb +0 -64
  502. data/lib/badline/sid/envelope/state.rb +0 -68
  503. data/lib/badline/sid/envelope.rb +0 -239
  504. data/lib/badline/sid/filter.rb +0 -263
  505. data/lib/badline/sid/voice.rb +0 -77
  506. data/lib/badline/sid/waveform/combined.rb +0 -126
  507. data/lib/badline/sid/waveform/fast_forward.rb +0 -105
  508. data/lib/badline/sid/waveform/noise_writeback.rb +0 -122
  509. data/lib/badline/sid/waveform/state.rb +0 -66
  510. data/lib/badline/sid/waveform.rb +0 -305
  511. data/lib/badline/sid.rb +0 -303
  512. data/lib/badline/snapshot/setup.rb +0 -75
  513. data/lib/badline/snapshot/state.rb +0 -189
  514. data/lib/badline/time_of_day.rb +0 -228
  515. data/lib/badline/traps.rb +0 -19
  516. data/lib/badline/via/control_lines.rb +0 -122
  517. data/lib/badline/via/fast_forward.rb +0 -53
  518. data/lib/badline/via/interrupt_register.rb +0 -67
  519. data/lib/badline/via/shift_register.rb +0 -180
  520. data/lib/badline/via/state.rb +0 -42
  521. data/lib/badline/via/timer1.rb +0 -131
  522. data/lib/badline/via/timer2.rb +0 -133
  523. data/lib/badline/via.rb +0 -266
  524. data/lib/badline/vic/bank.rb +0 -133
  525. data/lib/badline/vic/border_mask.rb +0 -86
  526. data/lib/badline/vic/collisions.rb +0 -115
  527. data/lib/badline/vic/color_patches.rb +0 -47
  528. data/lib/badline/vic/display_state.rb +0 -185
  529. data/lib/badline/vic/graphics_mode.rb +0 -164
  530. data/lib/badline/vic/graphics_shifter.rb +0 -207
  531. data/lib/badline/vic/register_log.rb +0 -81
  532. data/lib/badline/vic/registers.rb +0 -195
  533. data/lib/badline/vic/sequencer.rb +0 -275
  534. data/lib/badline/vic/sequencer_output.rb +0 -116
  535. data/lib/badline/vic/sequencer_state.rb +0 -89
  536. data/lib/badline/vic/sprite/internal_bus.rb +0 -68
  537. data/lib/badline/vic/sprite/saved_state.rb +0 -64
  538. data/lib/badline/vic/sprite/shifter.rb +0 -146
  539. data/lib/badline/vic/sprite/timing.rb +0 -56
  540. data/lib/badline/vic/sprite.rb +0 -337
  541. data/lib/badline/vic/sprites.rb +0 -282
  542. data/lib/badline/vic/sprites_state.rb +0 -57
  543. data/lib/badline/vic.rb +0 -853
  544. data/lib/badline/vic20/saved_state.rb +0 -124
  545. data/lib/badline/vic20/sound/output.rb +0 -83
  546. data/lib/badline/vic20/sound.rb +0 -233
  547. data/lib/badline/vic20/vic/saved_state.rb +0 -55
  548. data/lib/badline/z80/arithmetic.rb +0 -117
  549. data/lib/badline/z80/bit_page.rb +0 -55
  550. data/lib/badline/z80/block_io.rb +0 -58
  551. data/lib/badline/z80/block_transfers.rb +0 -59
  552. data/lib/badline/z80/bus_cycles.rb +0 -111
  553. data/lib/badline/z80/core.rb +0 -113
  554. data/lib/badline/z80/exchanges.rb +0 -50
  555. data/lib/badline/z80/extended_page.rb +0 -89
  556. data/lib/badline/z80/flags.rb +0 -37
  557. data/lib/badline/z80/interrupt_acceptance.rb +0 -46
  558. data/lib/badline/z80/jumps.rb +0 -82
  559. data/lib/badline/z80/loads.rb +0 -98
  560. data/lib/badline/z80/main_page.rb +0 -134
  561. data/lib/badline/z80/registers.rb +0 -120
  562. data/lib/badline/z80/saved_state.rb +0 -69
  563. data/lib/badline/z80/shifts.rb +0 -64
  564. data/lib/badline/z80.rb +0 -11
  565. /data/lib/badline/snapshot/{fields.rb → vice/fields.rb} +0 -0
@@ -0,0 +1,318 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "badline/media/extensions"
4
+ require "badline/drives/model1541/sector_reader"
5
+
6
+ module Badline
7
+ module Drives
8
+ class Model1541
9
+ # A disk as the head sees it: a Track for each half track that holds
10
+ # data, numbered from 2 (track 1) to MAX_HALF_TRACK (track 42), and
11
+ # nothing between them. A .d64 image fills the whole tracks; a .g64
12
+ # can fill any of them, with tracks of any length.
13
+ #
14
+ # The disk has a second side, for the 1571's second head, its half
15
+ # tracks numbered from SIDE on. A .d71 fills both sides, its tracks
16
+ # 36-70 being tracks 1-35 of the second, and a .g71 holds 84 half
17
+ # tracks a side. A 1541 reads the first side only.
18
+ #
19
+ # The head writes into the tracks' bytes, and a write to a half track
20
+ # without data gives it a blank Track first. A disk made from an image
21
+ # remembers it: flush reads the sectors back off the tracks written
22
+ # since the last flush and stores them in the image, which writes its
23
+ # host file. The disk is write-protected when the image won't take
24
+ # writes.
25
+ class Disk
26
+ # Whether the image is a .g64 or .g71, by its name.
27
+ def self.g64?(path) = Media::Extensions.kind(path) == :gcr
28
+
29
+ MAX_HALF_TRACK = 84
30
+
31
+ # Where the second side's half tracks start: its track 1 is SIDE + 2.
32
+ SIDE = MAX_HALF_TRACK + 1
33
+
34
+ # The tracks a side of a .d71 holds.
35
+ D71_SIDE_TRACKS = 35
36
+
37
+ # The half track entries a side of a .g71 holds.
38
+ G71_SIDE_ENTRIES = 84
39
+
40
+ # GCR bytes around a track in each speed zone (Storage::D64Image.zone):
41
+ # 200 ms of rotation at 300 rpm, in bytes of 32, 30, 28 and 26 µs.
42
+ TRACK_LENGTHS = [6250, 6666, 7142, 7692].freeze
43
+
44
+ # How far round the DOS's N: starts each track from the one before,
45
+ # in each zone, in 1/10000 of a turn: it steps in and starts writing
46
+ # the next track that long after it started the last.
47
+ SKEWS = [2916, 897, 8915, 6869].freeze
48
+ SKEW_TURN = 10_000
49
+
50
+ SYNC_LENGTH = 5
51
+ HEADER_GAP = 9
52
+ GAP = 0x55
53
+ HEADER_ID = 0x08
54
+ DATA_ID = 0x07
55
+
56
+ # A sector: SYNC, the 10-byte GCR header, the header gap, SYNC and
57
+ # the 325-byte GCR data block. The gap after it takes up the rest.
58
+ SECTOR_LENGTH = SYNC_LENGTH + 10 + HEADER_GAP + SYNC_LENGTH + 325
59
+
60
+ # A disk from the image at +path+: a .g64 or .g71 as its tracks are,
61
+ # a .d71 formatted on both sides, and anything else as a .d64,
62
+ # formatted. `read_only` opens the image write-protected.
63
+ def self.open(path, read_only: false)
64
+ image = image_for(path, read_only)
65
+ (Disk.g64?(path) ? from_g64(image) : from_d64(image)).opened(path, read_only)
66
+ end
67
+
68
+ # The image at +path+, by its name: a .g64 or .g71, a .d71, and
69
+ # anything else a .d64. `bytes` stands in for the file's contents.
70
+ def self.image_for(path, read_only, bytes = nil)
71
+ return Storage::G64Image.new(path, read_only:, bytes:) if Disk.g64?(path)
72
+ return Storage::D71Image.new(path, read_only:, bytes:) if File.extname(path).casecmp?(".d71")
73
+
74
+ Storage::D64Image.new(path, read_only:, bytes:)
75
+ end
76
+
77
+ # A disk from a G64 or G71 image, each half track as the image stores
78
+ # it, with its speed map. Flushing it writes the tracks back as they
79
+ # are.
80
+ def self.from_g64(image)
81
+ disk = new(image)
82
+ image.half_tracks.times do |entry|
83
+ track = image.track(entry)
84
+ disk.write(half_track_of(entry), Track.new(*track, image.speeds(entry))) if track
85
+ end
86
+ disk
87
+ end
88
+
89
+ # The half track a G64 or G71 table entry holds: entry 0 is track 1,
90
+ # and a .g71's entries from G71_SIDE_ENTRIES on are the second
91
+ # side's.
92
+ def self.half_track_of(entry)
93
+ side = entry / G71_SIDE_ENTRIES
94
+ (side * SIDE) + (entry % G71_SIDE_ENTRIES) + Mechanism::MIN_HALF_TRACK
95
+ end
96
+
97
+ # The table entry of a half track, or nil for one a G64 or G71 has
98
+ # no entry for.
99
+ def self.entry_of(half_track)
100
+ side = half_track / SIDE
101
+ entry = (half_track % SIDE) - Mechanism::MIN_HALF_TRACK
102
+ (side * G71_SIDE_ENTRIES) + entry if entry.between?(0, G71_SIDE_ENTRIES - 1)
103
+ end
104
+
105
+ # The half track an image's track number is on: a .d71's tracks past
106
+ # D71_SIDE_TRACKS are on the second side.
107
+ def self.d64_half_track(image, track)
108
+ return track * 2 unless image.sides == 2 && track > D71_SIDE_TRACKS
109
+
110
+ SIDE + ((track - D71_SIDE_TRACKS) * 2)
111
+ end
112
+
113
+ # The image's track number a half track holds, or nil for a half
114
+ # track or one past the image's last.
115
+ def self.d64_track(image, half_track)
116
+ side = half_track / SIDE
117
+ return unless (half_track % SIDE).even?
118
+
119
+ track = (half_track % SIDE) / 2
120
+ track += D71_SIDE_TRACKS if side == 1
121
+ track if track.positive? && track <= image.track_count && side < image.sides
122
+ end
123
+
124
+ # A disk formatted from a D64 image, each sector laid out as the DOS
125
+ # formats it, with the disk ID from the header block. A block the
126
+ # image's error table marks 20, 21, 22, 23, 27 or 29 is written the
127
+ # way that DOS error would read. The table's other codes, 24, 25, 26
128
+ # and 28, describe write or decoding faults the layout can't carry,
129
+ # so their blocks are written good and read without an error.
130
+ #
131
+ # Each track starts sector 0 at the angle the DOS's N: leaves it: track
132
+ # 1 at the index angle, and each track after it SKEWS[zone] of a turn
133
+ # round from the last. A .d71's second side starts over at the index
134
+ # angle.
135
+ def self.from_d64(image)
136
+ track, sector = image.header_block
137
+ header = image.read_block(track, sector)
138
+ id = [header[0xa2], header[0xa3]]
139
+ disk = new(image)
140
+ angle = 0
141
+ (1..image.track_count).each do |track|
142
+ half_track = d64_half_track(image, track)
143
+ zone = Storage::D64Image.zone((half_track % SIDE) / 2)
144
+ angle = half_track == SIDE + 2 ? 0 : (angle + SKEWS[zone]) % SKEW_TURN if track > 1
145
+ bytes = format_track(image, track, id, zone)
146
+ disk.write(half_track, Track.new(bytes.rotate(-(bytes.length * angle / SKEW_TURN)), zone))
147
+ end
148
+ disk
149
+ end
150
+
151
+ def self.format_track(image, track, id, zone = Storage::D64Image.zone(track))
152
+ sectors = image.sectors_in(track)
153
+ length = TRACK_LENGTHS[zone]
154
+ gap = (length - (sectors * SECTOR_LENGTH)) / sectors
155
+ bytes = []
156
+ sectors.times do |sector|
157
+ format_sector(bytes, image, track, sector, id)
158
+ gap.times { bytes << GAP }
159
+ end
160
+ bytes << GAP while bytes.length < length
161
+ bytes
162
+ end
163
+
164
+ # Error 21 leaves the block without SYNC marks, and 29 writes its
165
+ # header with the wrong first ID byte.
166
+ def self.format_sector(bytes, image, track, sector, id)
167
+ error = image.block_error(track, sector)
168
+ sync = error == 21 ? GAP : 0xff
169
+ id = [id[0] ^ 0xff, id[1]] if error == 29
170
+ SYNC_LENGTH.times { bytes << sync }
171
+ bytes.concat(GCR.encode(header(track, sector, id, error)))
172
+ HEADER_GAP.times { bytes << GAP }
173
+ SYNC_LENGTH.times { bytes << sync }
174
+ bytes.concat(GCR.encode(data_block(image.read_block(track, sector), error)))
175
+ end
176
+
177
+ # ID, checksum, sector, track, the second ID byte, the first and two
178
+ # $0F fillers. Error 20 hides the block's ID and 27 spoils its
179
+ # checksum.
180
+ def self.header(track, sector, id, error)
181
+ checksum = sector ^ track ^ id[1] ^ id[0]
182
+ checksum ^= 0xff if error == 27
183
+ [error == 20 ? 0x00 : HEADER_ID, checksum, sector, track, id[1], id[0], 0x0f, 0x0f]
184
+ end
185
+
186
+ # ID, 256 data bytes, their checksum and two $00 fillers. Error 22
187
+ # hides the block's ID and 23 spoils its checksum.
188
+ def self.data_block(data, error)
189
+ checksum = 0
190
+ data.each { |byte| checksum ^= byte }
191
+ checksum ^= 0xff if error == 23
192
+ block = [error == 22 ? 0x00 : DATA_ID]
193
+ block.concat(data)
194
+ block << checksum << 0x00 << 0x00
195
+ end
196
+
197
+ attr_reader :image
198
+
199
+ def initialize(image = nil)
200
+ @image = image
201
+ @tracks = Array.new(SIDE * 2)
202
+ @written = {}
203
+ @path = nil
204
+ @read_only = false
205
+ end
206
+
207
+ # The Track at the half track, or nil where there's no data.
208
+ def track(half_track) = @tracks[half_track]
209
+
210
+ def write(half_track, track)
211
+ @tracks[half_track] = track
212
+ end
213
+
214
+ # Whether the write-protect notch is covered: the disk's image won't
215
+ # take writes, or a disk without an image was opened read-only. A
216
+ # disk without an image keeps what it takes only in its tracks.
217
+ def write_protected? = @image.nil? ? @read_only : !@image.writable?
218
+
219
+ # A half track the head writes to, blank to begin with: no flux, and
220
+ # as many bytes as a turn holds at the bit rate of the +zone+ it's
221
+ # written in.
222
+ def writable_track(half_track, zone)
223
+ @tracks[half_track] ||= Track.new(Array.new(TRACK_LENGTHS[zone], 0), zone)
224
+ end
225
+
226
+ # The head wrote to the half track.
227
+ def written(half_track)
228
+ @written[half_track] = true
229
+ end
230
+
231
+ # Whether any track was written since the last flush.
232
+ def written? = !@written.empty?
233
+
234
+ # Stores each track written since the last flush in the image, all
235
+ # in one write to its host file. A G64 image takes the tracks as they
236
+ # are. A D64 image takes the sectors read back off them, and keeps
237
+ # whole tracks only, so a half track, or a track past its last, stays
238
+ # on the disk alone. A sector that no longer reads back keeps its old
239
+ # data, and the flush warns once, naming the tracks that lost one.
240
+ def flush
241
+ return flush_tracks if @image.respond_to?(:store_tracks)
242
+
243
+ tracks = @written.keys.filter_map do |half|
244
+ track = @image && Disk.d64_track(@image, half)
245
+ [half, track] if track
246
+ end
247
+ @written.clear
248
+ return if tracks.empty?
249
+
250
+ sectors = tracks.to_h { |half, track| [track, SectorReader.read(@tracks[half].bytes, track)] }
251
+ id = disk_id(sectors)
252
+ warn_lost(sectors)
253
+ @image.store_blocks(sectors.flat_map do |track, found|
254
+ Array.new(@image.sectors_in(track)) { |sector| stored_block(track, sector, found[sector], id) }
255
+ end)
256
+ end
257
+
258
+ private
259
+
260
+ def flush_tracks
261
+ tracks = @written.keys.sort.filter_map do |half|
262
+ entry = Disk.entry_of(half)
263
+ [entry, [@tracks[half].bytes, @tracks[half].zone]] if entry
264
+ end.to_h
265
+ @written.clear
266
+ @image.store_tracks(tracks) unless tracks.empty?
267
+ end
268
+
269
+ def warn_lost(sectors)
270
+ lost = sectors.keys.select do |track|
271
+ Array.new(@image.sectors_in(track)) { |sector| lost?(track, sector, sectors[track][sector]) }.any?
272
+ end
273
+ return if lost.empty?
274
+
275
+ warn "1541: tracks written that no longer read back whole: #{lost.join(', ')}. The .d64 keeps the old " \
276
+ "data of the sectors lost, and a .g64 image keeps tracks as the drive writes them."
277
+ end
278
+
279
+ # A sector the head no longer finds, which the image doesn't already
280
+ # hold as unreadable.
281
+ def lost?(track, sector, found)
282
+ found.nil? && ![20, 21].include?(@image.block_error(track, sector))
283
+ end
284
+
285
+ # The disk ID the headers must carry, from the header block's $A2 and
286
+ # $A3 as Disk.from_d64 takes it: as just read back, or as the image
287
+ # holds it.
288
+ def disk_id(sectors)
289
+ track, sector = @image.header_block
290
+ header = sectors.dig(track, sector)&.data
291
+ data = header ? header[1, 256] : @image.read_block(track, sector)
292
+ [data[0xa2], data[0xa3]]
293
+ end
294
+
295
+ # [track, sector, data, error] for DiskImage#store_blocks: the data as
296
+ # read, nil where it can't be, and the DOS error a read of it would
297
+ # raise, the way Disk.from_d64 writes each one. A sector whose header
298
+ # isn't found keeps its data, and reads as error 20, or 21 where the
299
+ # image already has it so.
300
+ def stored_block(track, sector, found, id)
301
+ return [track, sector, nil, @image.block_error(track, sector) == 21 ? 21 : 20] unless found
302
+
303
+ header = found.header
304
+ data = found.data
305
+ [track, sector, data && data[1, 256], block_error(header, data, id)]
306
+ end
307
+
308
+ def block_error(header, data, id)
309
+ if header[1] != header[2] ^ header[3] ^ header[4] ^ header[5] then 27
310
+ elsif header[5] != id[0] || header[4] != id[1] then 29
311
+ elsif data.nil? || data[0] != DATA_ID then 22
312
+ elsif data[257] != data[1, 256].reduce(:^) then 23
313
+ end
314
+ end
315
+ end
316
+ end
317
+ end
318
+ end
@@ -0,0 +1,38 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Badline
4
+ module Drives
5
+ class Model1541
6
+ # VIA 2, the one facing the disk mechanism. Its port B drives the
7
+ # Mechanism, and CB2 selects its read or write mode. The mechanism
8
+ # hears both after every write and reset, since ORB, DDRB, the ACR and
9
+ # the PCR can all change them.
10
+ class DiskVIA < Chips::VIA
11
+ attr_reader :mechanism
12
+
13
+ def initialize(start:, mechanism:)
14
+ @mechanism = mechanism
15
+ mechanism.via = self
16
+ super(start:, peripheral: mechanism)
17
+ end
18
+
19
+ def reset!
20
+ super
21
+ tell_mechanism
22
+ end
23
+
24
+ def poke(addr, value)
25
+ super
26
+ tell_mechanism
27
+ end
28
+
29
+ private
30
+
31
+ def tell_mechanism
32
+ @mechanism.port_b_written(port_b_output)
33
+ @mechanism.cb2_written(cb2_output)
34
+ end
35
+ end
36
+ end
37
+ end
38
+ end
@@ -0,0 +1,59 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Badline
4
+ module Drives
5
+ class Model1541
6
+ # Group code recording, the 1541's on-disk code. Each nibble becomes
7
+ # five bits, chosen so that no code has more than two 0 bits in a row
8
+ # and no run of codes has ten 1 bits in a row, which only a SYNC mark
9
+ # has. Four bytes become five.
10
+ module GCR
11
+ CODES = [0x0a, 0x0b, 0x12, 0x13, 0x0e, 0x0f, 0x16, 0x17,
12
+ 0x09, 0x19, 0x1a, 0x1b, 0x0d, 0x1d, 0x1e, 0x15].freeze
13
+
14
+ # Five-bit code => nibble, nil for the codes that aren't GCR.
15
+ NIBBLES = Array.new(32) { |code| CODES.index(code) }.freeze
16
+
17
+ module_function
18
+
19
+ # Encodes a whole number of four-byte groups.
20
+ def encode(bytes)
21
+ out = []
22
+ i = 0
23
+ while i < bytes.length
24
+ bits = 0
25
+ 4.times do |n|
26
+ byte = bytes[i + n]
27
+ bits = (bits << 10) | (CODES[byte >> 4] << 5) | CODES[byte & 0x0f]
28
+ end
29
+ 4.downto(0) { |n| out << ((bits >> (n * 8)) & 0xff) }
30
+ i += 4
31
+ end
32
+ out
33
+ end
34
+
35
+ # Decodes a whole number of five-byte groups, nil when any code
36
+ # isn't GCR.
37
+ def decode(bytes)
38
+ out = []
39
+ i = 0
40
+ while i < bytes.length
41
+ bits = 0
42
+ 5.times { |n| bits = (bits << 8) | bytes[i + n] }
43
+ n = 7
44
+ while n.positive?
45
+ high = NIBBLES[(bits >> (n * 5)) & 0x1f]
46
+ low = NIBBLES[(bits >> ((n - 1) * 5)) & 0x1f]
47
+ return nil if high.nil? || low.nil?
48
+
49
+ out << ((high << 4) | low)
50
+ n -= 2
51
+ end
52
+ i += 5
53
+ end
54
+ out
55
+ end
56
+ end
57
+ end
58
+ end
59
+ end
@@ -0,0 +1,251 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Badline
4
+ module Drives
5
+ class Model1541
6
+ class Disk
7
+ # The kinds of image a disk embeds, by name.
8
+ IMAGE_KINDS = %w[none d64 d71 g64].freeze
9
+
10
+ # A disk's fields in a keyed snapshot module (BL-D8-DISK): the image
11
+ # file's path and how it was opened, the image as last flushed,
12
+ # embedded under image and image.kind, each half track that differs
13
+ # from what the image decodes to, as track<H>.zone, track<H>.speeds
14
+ # and track<H>.gcr, raw GCR as the head sees it, and the half tracks
15
+ # written since the last flush.
16
+ module KeyedState
17
+ # The image's path, expanded, and whether it was opened read-only,
18
+ # for a disk from Disk.open. Nil for a disk made another way.
19
+ attr_reader :path, :read_only
20
+
21
+ # Notes the image's path and how it was opened.
22
+ def opened(path, read_only)
23
+ @path = File.expand_path(path)
24
+ @read_only = read_only
25
+ self
26
+ end
27
+
28
+ def save_fields(out)
29
+ path = @path
30
+ out.text("path", path) if path
31
+ out.bool("read_only", @read_only == true)
32
+ image = @image
33
+ decoded = nil
34
+ if image
35
+ out.text("image.kind", Disk.image_kind(image)).text("image.path", image.path.to_s)
36
+ out.bytes("image", image.contents)
37
+ decoded = Disk.decode(image)
38
+ end
39
+ @tracks.each_with_index { |track, half| save_track_fields(out, half, track, decoded) }
40
+ out.ints("written", @written.keys)
41
+ end
42
+
43
+ def load_track_fields(input)
44
+ @tracks.each_index do |half|
45
+ zone = input.optional_int("track#{half}.zone")
46
+ next if zone.nil?
47
+
48
+ speeds = input.ints("track#{half}.speeds", [])
49
+ gcr = input.bytes("track#{half}.gcr", "".b).bytes
50
+ @tracks[half] = Track.new(gcr, zone, speeds.empty? ? nil : speeds.dup)
51
+ end
52
+ @written.clear
53
+ input.ints("written", []).each { |half| @written[half] = true }
54
+ self
55
+ end
56
+
57
+ private
58
+
59
+ def save_track_fields(out, half, track, decoded)
60
+ return if track.nil?
61
+
62
+ original = decoded&.track(half)
63
+ return if original && original.zone == track.zone && original.speeds == track.speeds &&
64
+ original.bytes == track.bytes
65
+
66
+ out.int("track#{half}.zone", track.zone)
67
+ speeds = track.speeds
68
+ out.ints("track#{half}.speeds", speeds) if speeds
69
+ out.bytes("track#{half}.gcr", track.bytes)
70
+ end
71
+ end
72
+
73
+ include KeyedState
74
+
75
+ # The kind of `image`, by name.
76
+ def self.image_kind(image)
77
+ return "g64" if image.is_a?(Storage::G64Image)
78
+ return "d71" if image.is_a?(Storage::D71Image)
79
+
80
+ "d64"
81
+ end
82
+
83
+ # The tracks `image` holds, as an insert decodes them.
84
+ def self.decode(image) = image.is_a?(Storage::G64Image) ? from_g64(image) : from_d64(image)
85
+
86
+ # The disk a keyed snapshot module describes: its embedded image
87
+ # decoded, with the tracks the module holds laid over it. The image
88
+ # takes writes back to its file only while that file is there and
89
+ # the reader isn't `detached`. With no image, the disk is the
90
+ # tracks alone, write-protected.
91
+ def self.from_fields(input, detached: false)
92
+ path = input.text("path", "")
93
+ read_only = input.bool("read_only", false)
94
+ kind = input.choice("image.kind", IMAGE_KINDS, "none")
95
+ image_path = input.text("image.path", "")
96
+ disk = kind == "none" ? Disk.new : from_image(input, kind, image_path, read_only || detached)
97
+ disk.opened(path, read_only || kind == "none") unless path.empty?
98
+ disk.load_track_fields(input)
99
+ end
100
+
101
+ def self.from_image(input, kind, path, read_only)
102
+ bytes = input.bytes("image", "".b).bytes
103
+ writable_path = path.empty? || !File.file?(path) ? "" : path
104
+ image = case kind
105
+ when "g64" then Storage::G64Image.new(writable_path, read_only:, bytes:)
106
+ when "d71" then Storage::D71Image.new(writable_path, read_only:, bytes:)
107
+ else Storage::D64Image.new(writable_path, read_only:, bytes:)
108
+ end
109
+ decode(image)
110
+ end
111
+ end
112
+
113
+ class Mechanism
114
+ # The head, the motor, the stepper and the read and write
115
+ # electronics in a keyed snapshot module (BL-D8-MECH): where the
116
+ # head is, the disk's place in its turn, the read clock's next
117
+ # clock in cycles from now, the shift registers and the flags. The
118
+ # cell under the head, its width and the next event come from the
119
+ # track and the time on load, the cell period from the zone, and
120
+ # the write protection from the disk.
121
+ module KeyedState
122
+ def save_fields(out)
123
+ out.int("half_track", @half_track).int("slip", @slip).int("side", @side_base).int("zone", @zone)
124
+ out.int("time", @time).int("read_clock_in", @clock_at - @time).int("clocks", @clocks)
125
+ out.int("shift", @shift).int("ones", @ones).int("bits", @bits).int("write_shift", @write_shift)
126
+ out.bool("motor", @motor).bool("led", @led).bool("sync", @sync).bool("byte_ready", @byte_ready)
127
+ out.bool("byte_latched", @byte_latched).bool("writing", @writing).bool("write_gate", @write_gate)
128
+ out.bool("track_written", @track_written).optional_int("write_index", @write_index)
129
+ end
130
+
131
+ # Puts `disk` under the head without flushing the one there, as a
132
+ # snapshot load replaces it.
133
+ def load_fields(input, disk)
134
+ @disk = disk
135
+ load_head_fields(input)
136
+ load_flag_fields(input)
137
+ @clock = Track.cell(@zone)
138
+ @protected = disk&.write_protected? || false
139
+ track_written = input.bool("track_written", false)
140
+ write_index = input.optional_int("write_index")
141
+ load_track(@time)
142
+ @track_written = track_written
143
+ @write_index = write_index
144
+ end
145
+
146
+ private
147
+
148
+ def load_head_fields(input)
149
+ @half_track = input.int("half_track", @half_track)
150
+ @slip = input.masked("slip", @slip, 0x03)
151
+ @side_base = input.int("side", @side_base)
152
+ @zone = input.masked("zone", @zone, 0x03)
153
+ @time = input.int("time", @time)
154
+ @clock_at = @time + input.int("read_clock_in", @clock_at - @time)
155
+ @clocks = input.int("clocks", @clocks)
156
+ @shift = input.int("shift", @shift)
157
+ @ones = input.int("ones", @ones)
158
+ @bits = input.int("bits", @bits)
159
+ @write_shift = input.int("write_shift", @write_shift)
160
+ end
161
+
162
+ def load_flag_fields(input)
163
+ @motor = input.bool("motor", @motor)
164
+ @led = input.bool("led", @led)
165
+ @sync = input.bool("sync", @sync)
166
+ @byte_ready = input.bool("byte_ready", @byte_ready)
167
+ @byte_latched = input.bool("byte_latched", @byte_latched)
168
+ @writing = input.bool("writing", @writing)
169
+ @write_gate = input.bool("write_gate", @write_gate)
170
+ end
171
+ end
172
+
173
+ include KeyedState
174
+ end
175
+
176
+ class Bus
177
+ # The drive's RAM and the byte last on its data bus, in a keyed
178
+ # snapshot module (BL-D8-RAM).
179
+ def save_fields(out)
180
+ out.int("data", @data).bytes("ram", @ram.read(0, @ram.length))
181
+ end
182
+
183
+ def load_fields(input)
184
+ @data = input.byte("data", @data)
185
+ ram = @ram.read(0, @ram.length)
186
+ @ram.write(0, input.bytes_into("ram", ram))
187
+ @watching = false
188
+ end
189
+ end
190
+
191
+ # The drive's own fields in a keyed snapshot module (BL-D8): its model,
192
+ # its clock phase and cycle count, the SO pulse due, and the host's
193
+ # lines as the serial port latched them. A save settles the drive first, so it is awake;
194
+ # a load forgets its orbits.
195
+ module KeyedState
196
+ def save_fields(out)
197
+ settle!
198
+ out.text("model", model_name).int("phase", @phase).int("cycles", @cycles)
199
+ out.bool("so_pending", @mechanism.so_pending)
200
+ @serial_port.save_fields(out, "serial.")
201
+ out
202
+ end
203
+
204
+ def load_fields(input)
205
+ settle!
206
+ input.choice("model", [model_name], model_name)
207
+ @phase = input.int("phase", @phase)
208
+ @cycles = input.int("cycles", @cycles)
209
+ @mechanism.so_pending = input.bool("so_pending", @mechanism.so_pending)
210
+ @serial_port.load_fields(input, "serial.")
211
+ end
212
+
213
+ def restored! = forget_orbits
214
+
215
+ # The drive as modules of its own, named from `name` (BL-D8 for
216
+ # device 8): its own fields, its RAM, each VIA, the CPU, the
217
+ # mechanism and the disk, when one is in.
218
+ def save_modules(sections, name)
219
+ sections << Snapshot::Keyed.section(name) { |out| save_fields(out) }
220
+ sections << Snapshot::Keyed.section("#{name}-RAM") { |out| @bus.save_fields(out) }
221
+ sections << Snapshot::Keyed.section("#{name}-VIA1") { |out| @via1.save_fields(out) }
222
+ sections << Snapshot::Keyed.section("#{name}-VIA2") { |out| @via2.save_fields(out) }
223
+ sections << Snapshot::Keyed.section("#{name}-CPU") { |out| @cpu.save_fields(out) }
224
+ sections << Snapshot::Keyed.section("#{name}-MECH") { |out| @mechanism.save_fields(out) }
225
+ disk = @mechanism.disk
226
+ sections << Snapshot::Keyed.section("#{name}-DISK") { |out| disk.save_fields(out) } if disk
227
+ sections
228
+ end
229
+
230
+ # The drive from its modules, `input` being its own. No disk module
231
+ # means no disk in the drive.
232
+ def load_modules(modules, input, name)
233
+ load_fields(input)
234
+ @bus.load_fields(modules.required("#{name}-RAM"))
235
+ @via1.load_fields(modules.required("#{name}-VIA1"))
236
+ @via2.load_fields(modules.required("#{name}-VIA2"))
237
+ @cpu.load_fields(modules.required("#{name}-CPU"))
238
+ disk = modules.optional("#{name}-DISK")
239
+ mechanism = modules.required("#{name}-MECH")
240
+ @mechanism.load_fields(mechanism, disk.nil? ? nil : Disk.from_fields(disk, detached: modules.detached?))
241
+ @via1.restored!
242
+ @via2.restored!
243
+ @cpu.restored!
244
+ restored!
245
+ end
246
+ end
247
+
248
+ include KeyedState
249
+ end
250
+ end
251
+ end