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,126 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Badline
4
+ module Drives
5
+ class Model1581
6
+ # The drive CPU's bus, as the 74LS139 decodes A13-A15 (1581 Service
7
+ # Manual, memory map and schematic sheet 1):
8
+ #
9
+ # $0000-$1FFF - 8 KB of RAM
10
+ # $2000-$3FFF - nothing: open bus
11
+ # $4000-$5FFF - the 8520, its sixteen registers mirrored
12
+ # $6000-$7FFF - the WD1772, its four registers mirrored
13
+ # $8000-$FFFF - the DOS ROM
14
+ #
15
+ # Writes to the ROM and to open bus go nowhere, and a read from open
16
+ # bus finds the last byte on the data bus, as on the 1541. A write to
17
+ # either of the 8520's ports or their direction registers moves the
18
+ # drive's lines (Drives::Model1581#ports_written).
19
+ #
20
+ # While Idle records a pass of the DOS's idle loop, the bus watches it
21
+ # (watch!): the RAM it reads and writes, and whether it touched the
22
+ # 8520 or the WD1772, either of which makes the pass volatile. While
23
+ # Idle looks for an orbit, the bus guards the drive (guard!), and any
24
+ # access to either chip taints it.
25
+ class Bus
26
+ include Addressable
27
+
28
+ attr_reader :ram, :rom, :cia, :fdc, :data, :volatile, :touched, :tainted, :touched_timers
29
+
30
+ def initialize(rom:, cia:, fdc:, drive:)
31
+ addressable_at(0, length: 2**16)
32
+ @ram = Memory.new([], length: 0x2000, start: 0)
33
+ @rom = rom
34
+ @cia = cia
35
+ @fdc = fdc
36
+ @drive = drive
37
+ @data = 0
38
+ @recording = @guarding = @watching = false
39
+ @volatile = @tainted = false
40
+ @touched = {}
41
+ @touched_timers = 0
42
+ end
43
+
44
+ def peek(addr)
45
+ return @data = @rom.peek(addr) if addr >= 0x8000
46
+ return @data = chip_peek(addr) if addr >= 0x2000
47
+
48
+ touch(addr) if @recording
49
+ @data = @ram.peek(addr)
50
+ end
51
+
52
+ def poke(addr, value)
53
+ @data = value
54
+ return if addr >= 0x8000
55
+ return chip_poke(addr, value) if addr >= 0x2000
56
+
57
+ touch(addr) if @recording
58
+ @ram.poke(addr, value)
59
+ end
60
+
61
+ # Starts watching: see volatile and touched.
62
+ def watch!
63
+ @recording = true
64
+ @volatile = false
65
+ @touched = {}
66
+ watching!
67
+ end
68
+
69
+ def unwatch!
70
+ @recording = false
71
+ watching!
72
+ end
73
+
74
+ # Starts guarding: see tainted.
75
+ def guard!
76
+ @guarding = true
77
+ @tainted = false
78
+ watching!
79
+ end
80
+
81
+ private
82
+
83
+ def chip_peek(addr)
84
+ watch_chip if @watching
85
+ if addr >= 0x6000
86
+ @fdc.peek(addr & 0x03)
87
+ elsif addr >= 0x4000
88
+ @cia.peek(0x4000 | (addr & 0x0f))
89
+ else
90
+ @data
91
+ end
92
+ end
93
+
94
+ def chip_poke(addr, value)
95
+ watch_chip if @watching
96
+ if addr >= 0x6000
97
+ @fdc.poke(addr & 0x03, value)
98
+ elsif addr >= 0x4000
99
+ @cia.poke(0x4000 | (addr & 0x0f), value)
100
+ @drive.ports_written if addr.nobits?(0x0c)
101
+ end
102
+ end
103
+
104
+ def touch(addr)
105
+ @touched[addr] = @ram.peek(addr) unless @touched.key?(addr)
106
+ end
107
+
108
+ def watching!
109
+ @watching = @recording || @guarding
110
+ end
111
+
112
+ def watch_chip
113
+ if @recording
114
+ @volatile = true
115
+ @recording = false
116
+ end
117
+ if @guarding
118
+ @tainted = true
119
+ @guarding = false
120
+ end
121
+ watching!
122
+ end
123
+ end
124
+ end
125
+ end
126
+ end
@@ -0,0 +1,13 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Badline
4
+ module Drives
5
+ class Model1581
6
+ # The drive's 6502, on the drive's Bus.
7
+ class CPU
8
+ include Badline::Chips::CPU::Core
9
+ include Engine::IdleCPU
10
+ end
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,133 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Badline
4
+ module Drives
5
+ class Model1581
6
+ # A 3.5" double-sided disk as the WD1772 sees it: a Track on each side
7
+ # of 80 cylinders, or none where it was never formatted.
8
+ #
9
+ # A .d81 image holds the 1581 DOS's logical blocks in order, 40 of 256
10
+ # bytes on each logical track 1 to 80 (1581 Service Manual, format
11
+ # organization). Logical track t is cylinder t - 1, and the DOS puts
12
+ # its sectors 0 to 19 on side 0 and 20 to 39 on side 1, two to each
13
+ # 512-byte physical sector, numbered from 1. So physical sector s on
14
+ # side h of cylinder c holds logical sectors 20h + 2(s - 1) and the one
15
+ # after it, and every cylinder of the image is a standard Track
16
+ # (Track.standard) on both sides. A block the image's error table
17
+ # marks 20 or 21 loses its sector's ID field, 27 spoils the ID's CRC,
18
+ # 22 loses the data field and 23 spoils its CRC.
19
+ #
20
+ # The WD1772 writes into the tracks, and flush stores the standard
21
+ # sectors of each one written back in the image, which writes its host
22
+ # file. A track formatted another way stays on the disk alone. The disk
23
+ # is write-protected when the image won't take writes.
24
+ class Disk
25
+ CYLINDERS = 80
26
+ SIDES = 2
27
+
28
+ # Logical blocks a side of a cylinder holds.
29
+ SIDE_BLOCKS = 20
30
+
31
+ # A logical block's bytes, and how many a physical sector holds.
32
+ BLOCK_SIZE = 256
33
+ BLOCKS = Track::SECTOR_SIZE / BLOCK_SIZE
34
+
35
+ # A disk from the .d81 image at +path+. `read_only` opens the image
36
+ # write-protected.
37
+ def self.open(path, read_only: false)
38
+ new(Storage::D81Image.new(path, read_only:)).opened(path, read_only)
39
+ end
40
+
41
+ attr_reader :image
42
+
43
+ def initialize(image = nil)
44
+ @image = image
45
+ @tracks = Array.new(CYLINDERS * SIDES)
46
+ @written = {}
47
+ @changed = {}
48
+ @path = nil
49
+ @read_only = false
50
+ end
51
+
52
+ # The Track on +side+ of +cylinder+, or nil where there is none: past
53
+ # the image's cylinders, or on a disk without an image that the head
54
+ # never formatted.
55
+ def track(cylinder, side)
56
+ index = (cylinder * SIDES) + side
57
+ return @tracks[index] if @tracks[index] || !@image || cylinder >= CYLINDERS
58
+
59
+ @tracks[index] = image_track(cylinder, side)
60
+ end
61
+
62
+ # A write track laid +track+ down on +side+ of +cylinder+, or a write
63
+ # sector changed it.
64
+ def write(cylinder, side, track)
65
+ return if cylinder >= CYLINDERS
66
+
67
+ index = (cylinder * SIDES) + side
68
+ @tracks[index] = track
69
+ @written[index] = true
70
+ @changed[index] = true
71
+ end
72
+
73
+ # Whether the write-protect hole is open: the disk's image won't take
74
+ # writes, or a disk without an image was opened read-only.
75
+ def write_protected? = @image.nil? ? @read_only : !@image.writable?
76
+
77
+ # Whether any track was written since the last flush.
78
+ def written? = !@written.empty?
79
+
80
+ # Stores the standard sectors of each track written since the last
81
+ # flush in the image, all in one write to its host file.
82
+ def flush
83
+ written = @written.keys.sort
84
+ @written.clear
85
+ return if @image.nil? || written.empty?
86
+
87
+ @image.store_blocks(written.flat_map { |index| track_blocks(index / SIDES, index % SIDES) })
88
+ end
89
+
90
+ private
91
+
92
+ def image_track(cylinder, side)
93
+ track = Track.standard(cylinder, side) { |sector| physical_block(cylinder, side, sector) }
94
+ track.sectors.each { |sector| mark_errors(cylinder, side, sector) }
95
+ track.sectors.reject! { |sector| sector.id.nil? }
96
+ track
97
+ end
98
+
99
+ # The logical track and first sector physical sector +sector+ holds.
100
+ def logical(cylinder, side, sector) = [cylinder + 1, (side * SIDE_BLOCKS) + ((sector - 1) * BLOCKS)]
101
+
102
+ def physical_block(cylinder, side, sector)
103
+ track, first = logical(cylinder, side, sector)
104
+ Array.new(BLOCKS) { |i| @image.read_block(track, first + i) || [] }.flatten
105
+ end
106
+
107
+ def mark_errors(cylinder, side, sector)
108
+ track, first = logical(cylinder, side, sector.id.bytes[2])
109
+ errors = Array.new(BLOCKS) { |i| @image.block_error(track, first + i) }
110
+ sector.id.good = false if errors.include?(27)
111
+ sector.data.good = false if errors.include?(23)
112
+ sector.data = nil if errors.include?(22)
113
+ sector.id = nil if errors.include?(20) || errors.include?(21)
114
+ end
115
+
116
+ # [track, sector, data, nil] for DiskImage#store_blocks, for each
117
+ # logical block a standard sector of the track holds.
118
+ def track_blocks(cylinder, side)
119
+ track = @tracks[(cylinder * SIDES) + side]
120
+ Track::SECTORS.times.flat_map do |index|
121
+ data = track.sector([cylinder, side, index + 1, Track::SIZE_CODE])&.data
122
+ next [] unless data
123
+
124
+ logical_track, first = logical(cylinder, side, index + 1)
125
+ Array.new(BLOCKS) do |i|
126
+ [logical_track, first + i, data.bytes[i * BLOCK_SIZE, BLOCK_SIZE], nil]
127
+ end
128
+ end
129
+ end
130
+ end
131
+ end
132
+ end
133
+ end
@@ -0,0 +1,280 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Badline
4
+ module Drives
5
+ class Model1581
6
+ class WD1772
7
+ PHASE_NAMES = %w[idle spin_up stepped settled search read_data write_gate write_data read_track track_index
8
+ write_track motor_idle index_interrupt settle_search read_address write_track_start
9
+ starting].freeze
10
+ MODE_NAMES = %w[read write address verify_track].freeze
11
+
12
+ # The controller in a keyed snapshot module (BL-D8-FDC): its own
13
+ # cycle count, the step of the command running and what the search
14
+ # is for by name, the registers and lines, the command's counters,
15
+ # and when its next step, its deadline and the next ID field fall,
16
+ # in its cycles from now, each absent when it never does.
17
+ def save_fields(out)
18
+ out.int("now", @now).text("phase", PHASE_NAMES.fetch(@phase)).text("mode", MODE_NAMES.fetch(@mode))
19
+ [["due_in", @due], ["deadline_in", @deadline], ["id_in", @id_at]].each do |key, at|
20
+ out.int(key, at - @now) unless at == NEVER
21
+ end
22
+ out.int("command", @command).int("track", @track).int("sector", @sector).int("data", @data)
23
+ out.int("status", @status).int("steps", @steps).int("count", @count).int("after_spin_up", @after_spin_up)
24
+ out.int("found", @found).int("index", @index).int("crc", @crc).int("write_id", @write_id).int("size", @size)
25
+ save_flag_fields(out)
26
+ out.bytes("bytes", @bytes).ints("syncs", @syncs.map { |sync| sync ? 1 : 0 })
27
+ end
28
+
29
+ def load_fields(input)
30
+ @now = input.int("now", @now)
31
+ @phase = PHASE_NAMES.index(input.choice("phase", PHASE_NAMES, "idle")).to_i
32
+ @mode = MODE_NAMES.index(input.choice("mode", MODE_NAMES, "read")).to_i
33
+ @due = at_fields(input, "due_in")
34
+ @deadline = at_fields(input, "deadline_in")
35
+ @id_at = at_fields(input, "id_in")
36
+ load_register_fields(input)
37
+ load_flag_fields(input)
38
+ @bytes = input.bytes("bytes", "".b).bytes
39
+ @syncs = input.ints("syncs", []).map { |sync| sync == 1 }
40
+ end
41
+
42
+ private
43
+
44
+ def at_fields(input, key)
45
+ cycles = input.optional_int(key)
46
+ cycles.nil? ? NEVER : @now + cycles
47
+ end
48
+
49
+ def load_register_fields(input)
50
+ @command = input.byte("command", @command)
51
+ @track = input.byte("track", @track)
52
+ @sector = input.byte("sector", @sector)
53
+ @data = input.byte("data", @data)
54
+ @status = input.byte("status", @status)
55
+ @steps = input.int("steps", @steps)
56
+ @count = input.int("count", @count)
57
+ @after_spin_up = input.int("after_spin_up", @after_spin_up)
58
+ @found = input.int("found", @found)
59
+ @index = input.int("index", @index)
60
+ @crc = input.int("crc", @crc)
61
+ @write_id = input.int("write_id", @write_id)
62
+ @size = input.int("size", @size)
63
+ end
64
+
65
+ def save_flag_fields(out)
66
+ out.bool("type1", @type1).bool("drq", @drq).bool("intrq", @intrq).bool("motor_on", @motor_on)
67
+ out.bool("inward", @inward).bool("crc_good", @crc_good).bool("gate_open", @gate_open).bool("late", @late)
68
+ out.bool("settling", @settling)
69
+ end
70
+
71
+ def load_flag_fields(input)
72
+ @type1 = input.bool("type1", @type1)
73
+ @drq = input.bool("drq", @drq)
74
+ @intrq = input.bool("intrq", @intrq)
75
+ @motor_on = input.bool("motor_on", @motor_on)
76
+ @inward = input.bool("inward", @inward)
77
+ @crc_good = input.bool("crc_good", @crc_good)
78
+ @gate_open = input.bool("gate_open", @gate_open)
79
+ @late = input.bool("late", @late)
80
+ @settling = input.bool("settling", @settling)
81
+ end
82
+ end
83
+
84
+ class Mechanism
85
+ # The head, the motor and the disk-change switch in a keyed snapshot
86
+ # module (BL-D8-MECH), with the disk's angle as it stands `now`, on
87
+ # the controller's clock.
88
+ def save_fields(out, now)
89
+ out.int("cylinder", @cylinder).int("side", @side).int("angle", angle(now)).bool("motor", @motor)
90
+ out.bool("changed", @changed)
91
+ end
92
+
93
+ # Puts `disk` under the head without flushing the one there, as a
94
+ # snapshot load replaces it.
95
+ def load_fields(input, disk, now)
96
+ @disk = disk
97
+ @cylinder = input.int("cylinder", @cylinder)
98
+ @side = input.masked("side", @side, 1)
99
+ @angle = input.int("angle", @angle)
100
+ @since = now
101
+ @motor = input.bool("motor", @motor)
102
+ @changed = input.bool("changed", @changed)
103
+ end
104
+ end
105
+
106
+ class Disk
107
+ # A disk's fields in a keyed snapshot module (BL-D8-DISK): the image
108
+ # file's path and how it was opened, the image as last flushed,
109
+ # embedded, each track the head wrote since the disk went in, sector
110
+ # by sector as the WD1772 sees them, and the tracks written since the
111
+ # last flush.
112
+ module KeyedState
113
+ # The image's path, expanded, and whether it was opened read-only,
114
+ # for a disk from Disk.open. Nil for a disk made another way.
115
+ attr_reader :path, :read_only
116
+
117
+ # Notes the image's path and how it was opened.
118
+ def opened(path, read_only)
119
+ @path = File.expand_path(path)
120
+ @read_only = read_only
121
+ self
122
+ end
123
+
124
+ def save_fields(out)
125
+ path = @path
126
+ out.text("path", path) if path
127
+ out.bool("read_only", @read_only == true)
128
+ image = @image
129
+ out.text("image.path", image.path.to_s).bytes("image", image.contents) if image
130
+ changed = @changed.keys.sort
131
+ out.ints("changed", changed)
132
+ changed.each { |index| save_track_fields(out, "track#{index}.", @tracks[index]) }
133
+ out.ints("written", @written.keys)
134
+ end
135
+
136
+ def load_track_fields(input)
137
+ @changed.clear
138
+ input.ints("changed", []).each do |index|
139
+ @tracks[index] = load_track_sectors(input, "track#{index}.")
140
+ @changed[index] = true
141
+ end
142
+ @written.clear
143
+ input.ints("written", []).each { |index| @written[index] = true }
144
+ self
145
+ end
146
+
147
+ private
148
+
149
+ def save_track_fields(out, prefix, track)
150
+ out.int("#{prefix}sectors", track.sectors.length)
151
+ track.sectors.each_with_index do |sector, number|
152
+ save_field_fields(out, "#{prefix}sector#{number}.id.", sector.id)
153
+ data = sector.data
154
+ save_field_fields(out, "#{prefix}sector#{number}.data.", data) if data
155
+ end
156
+ end
157
+
158
+ def save_field_fields(out, prefix, field)
159
+ out.int("#{prefix}mark", field.mark).bytes("#{prefix}bytes", field.bytes)
160
+ out.bool("#{prefix}good", field.good).bool("#{prefix}deleted", field.deleted)
161
+ end
162
+
163
+ def load_track_sectors(input, prefix)
164
+ Track.new(Array.new(input.int("#{prefix}sectors", 0)) do |number|
165
+ id = load_field_fields(input, "#{prefix}sector#{number}.id.")
166
+ data = input.optional_int("#{prefix}sector#{number}.data.mark")
167
+ Track::Sector.new(id, data.nil? ? nil : load_field_fields(input, "#{prefix}sector#{number}.data."))
168
+ end)
169
+ end
170
+
171
+ def load_field_fields(input, prefix)
172
+ Track::Field.new(input.int("#{prefix}mark", 0), input.bytes("#{prefix}bytes", "".b).bytes,
173
+ input.bool("#{prefix}good", true), input.bool("#{prefix}deleted", false))
174
+ end
175
+ end
176
+
177
+ include KeyedState
178
+
179
+ # The disk a keyed snapshot module describes: its embedded image,
180
+ # with the tracks the module holds laid over it. The image takes
181
+ # writes back to its file only while that file is there and the
182
+ # reader isn't `detached`. With no image, the disk is the tracks
183
+ # alone.
184
+ def self.from_fields(input, detached: false)
185
+ path = input.text("path", "")
186
+ read_only = input.bool("read_only", false)
187
+ bytes = input.bytes("image", "".b).bytes
188
+ image_path = input.text("image.path", "")
189
+ disk = bytes.empty? ? Disk.new : Disk.new(image_from(image_path, bytes, read_only || detached))
190
+ disk.opened(path, read_only || bytes.empty?) unless path.empty?
191
+ disk.load_track_fields(input)
192
+ end
193
+
194
+ def self.image_from(path, bytes, read_only)
195
+ Storage::D81Image.new(File.file?(path) ? path : "", read_only:, bytes:)
196
+ end
197
+ end
198
+
199
+ class Bus
200
+ # The drive's RAM and the byte last on its data bus, in a keyed
201
+ # snapshot module (BL-D8-RAM).
202
+ def save_fields(out)
203
+ out.int("data", @data).bytes("ram", @ram.read(0, @ram.length))
204
+ end
205
+
206
+ def load_fields(input)
207
+ @data = input.byte("data", @data)
208
+ ram = @ram.read(0, @ram.length)
209
+ @ram.write(0, input.bytes_into("ram", ram))
210
+ @watching = false
211
+ end
212
+ end
213
+
214
+ # The drive's own fields in a keyed snapshot module (BL-D8): its
215
+ # model, its clock phase and cycle count, the ATN level it last saw
216
+ # and the host's lines as the serial port latched them. The fast
217
+ # serial direction and the ports' lines come from the 8520 once it
218
+ # is loaded.
219
+ module KeyedState
220
+ def save_fields(out)
221
+ settle!
222
+ out.text("model", model_name).int("phase", @phase).int("cycles", @cycles).bool("atn", @atn)
223
+ @serial_port.save_fields(out, "serial.")
224
+ out
225
+ end
226
+
227
+ def load_fields(input)
228
+ settle!
229
+ input.choice("model", [model_name], model_name)
230
+ @phase = input.int("phase", @phase)
231
+ @cycles = input.int("cycles", @cycles)
232
+ @atn = input.bool("atn", @atn)
233
+ @serial_port.load_fields(input, "serial.")
234
+ end
235
+
236
+ def restored!
237
+ @fast_serial_out = @cia.port_b_lines.anybits?(FAST_SERIAL_OUT)
238
+ ports_written
239
+ push_fast_output(@fast_serial_out ? fast_pins : 0)
240
+ forget_orbits
241
+ end
242
+
243
+ # The drive as modules of its own, as the 1541's, with the 8520 and
244
+ # the WD1772 in place of the VIAs.
245
+ def save_modules(sections, name)
246
+ sections << Snapshot::Keyed.section(name) { |out| save_fields(out) }
247
+ sections << Snapshot::Keyed.section("#{name}-RAM") { |out| @bus.save_fields(out) }
248
+ sections << Snapshot::Keyed.section("#{name}-CIA") { |out| @cia.save_fields(out) }
249
+ sections << Snapshot::Keyed.section("#{name}-CPU") { |out| @cpu.save_fields(out) }
250
+ sections << Snapshot::Keyed.section("#{name}-FDC") { |out| @fdc.save_fields(out) }
251
+ now = @fdc.now
252
+ sections << Snapshot::Keyed.section("#{name}-MECH") { |out| @mechanism.save_fields(out, now) }
253
+ disk = @mechanism.disk
254
+ sections << Snapshot::Keyed.section("#{name}-DISK") { |out| disk.save_fields(out) } if disk
255
+ sections
256
+ end
257
+
258
+ # The drive from its modules, `input` being its own. The mechanism
259
+ # takes the disk's angle on the WD1772's clock, so it loads after
260
+ # the controller.
261
+ def load_modules(modules, input, name)
262
+ load_fields(input)
263
+ @bus.load_fields(modules.required("#{name}-RAM"))
264
+ @cia.load_fields(modules.required("#{name}-CIA"))
265
+ @cpu.load_fields(modules.required("#{name}-CPU"))
266
+ @fdc.load_fields(modules.required("#{name}-FDC"))
267
+ disk = modules.optional("#{name}-DISK")
268
+ mechanism = modules.required("#{name}-MECH")
269
+ @mechanism.load_fields(mechanism, disk.nil? ? nil : Disk.from_fields(disk, detached: modules.detached?),
270
+ @fdc.now)
271
+ @cia.restored!
272
+ @cpu.restored!
273
+ restored!
274
+ end
275
+ end
276
+
277
+ include KeyedState
278
+ end
279
+ end
280
+ end
@@ -0,0 +1,98 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Badline
4
+ module Drives
5
+ class Model1581
6
+ # The 1581's 3.5" mechanism: the spindle motor, the head on one of
7
+ # its cylinders and sides, the index sensor, and the lines it reports
8
+ # on the drive connector (1581 Service Manual, schematic sheet 2).
9
+ #
10
+ # The motor runs while /MOTOR is low and turns a disk at 300 rpm, a
11
+ # turn every REVOLUTION drive cycles at 2 MHz. The index sensor pulses
12
+ # for INDEX_PULSE cycles at the start of each turn, which is where a
13
+ # Track's bytes start. /RDY is low while the motor turns a disk. The
14
+ # WD1772 steps the head, and /TR00 is low while it sits on cylinder 0.
15
+ # /WPRT is low while the disk's write-protect hole is open, or without
16
+ # a disk.
17
+ #
18
+ # /DISK CHNG goes low as a disk comes out, and at power-on, and goes
19
+ # high again at a step pulse with a disk in, as on any Shugart-style
20
+ # drive with a disk change line. The DOS steps in and out after
21
+ # spinning the motor up to find out whether a disk is in.
22
+ #
23
+ # Times are the WD1772's cycle count (WD1772#now), which the drive
24
+ # passes in: the angle under the head follows from how long the disk
25
+ # has turned.
26
+ class Mechanism
27
+ REVOLUTION = 400_000
28
+ INDEX_PULSE = 4_000
29
+ LAST_CYLINDER = 83
30
+
31
+ attr_reader :disk, :cylinder
32
+ attr_accessor :side
33
+
34
+ def initialize
35
+ @disk = nil
36
+ @cylinder = 0
37
+ @side = 0
38
+ @motor = false
39
+ @changed = true
40
+ @angle = 0
41
+ @since = 0
42
+ end
43
+
44
+ # Puts a Disk in, or with nil takes it out. Whatever was in is
45
+ # flushed first, and the disk change line goes low.
46
+ def insert(disk, now)
47
+ @angle = angle(now)
48
+ @since = now
49
+ flush
50
+ @disk = disk
51
+ @changed = true
52
+ end
53
+
54
+ def flush = @disk&.flush
55
+
56
+ def motor_on? = @motor
57
+
58
+ # Turns the motor on or off at +now+.
59
+ def motor(on, now)
60
+ @angle = angle(now)
61
+ @since = now
62
+ @motor = on
63
+ end
64
+
65
+ # Whether a disk turns.
66
+ def spinning? = @motor && !@disk.nil?
67
+
68
+ # How far into its turn the disk is at +now+, in cycles from the
69
+ # index.
70
+ def angle(now) = spinning? ? (@angle + now - @since) % REVOLUTION : @angle
71
+
72
+ def index?(now) = spinning? && angle(now) < INDEX_PULSE
73
+
74
+ def ready? = spinning?
75
+
76
+ def track0? = @cylinder.zero?
77
+
78
+ def disk_changed? = @changed
79
+
80
+ def write_protected? = @disk.nil? || @disk.write_protected?
81
+
82
+ # A step pulse, towards the hub with +inward+.
83
+ def step(inward)
84
+ @cylinder = (@cylinder + (inward ? 1 : -1)).clamp(0, LAST_CYLINDER)
85
+ @changed = false if @disk
86
+ end
87
+
88
+ # The Track under the head, or nil.
89
+ def track = @disk&.track(@cylinder, @side)
90
+
91
+ # Stores a track the WD1772 laid down under the head.
92
+ def write(track)
93
+ @disk&.write(@cylinder, @side, track)
94
+ end
95
+ end
96
+ end
97
+ end
98
+ end