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
@@ -20,6 +20,7 @@ module Badline
20
20
  def initialize(computer, options)
21
21
  @computer = computer
22
22
  @at_finish = options.save_snapshot
23
+ @vice = options.vice?
23
24
  @autosave = options.frames.zero?
24
25
  @autosave_at = AUTOSAVE_FRAMES
25
26
  end
@@ -128,11 +129,21 @@ module Badline
128
129
  end
129
130
 
130
131
  def finish
131
- save(@at_finish) unless @at_finish.empty?
132
+ return if @at_finish.empty?
133
+
134
+ @vice ? export(@at_finish) : save(@at_finish)
132
135
  end
133
136
 
134
137
  private
135
138
 
139
+ # Saves the machine with VICE's modules too, and warns when it can't.
140
+ def export(path)
141
+ @computer.export_snapshot(path)
142
+ puts "Saved #{path} for badline and VICE"
143
+ rescue Snapshot::FormatError, SystemCallError => e
144
+ warn "badline: #{path}: #{e.message}"
145
+ end
146
+
136
147
  def restore_from(path)
137
148
  @computer = Snapshot.load(path) { |line| puts line }
138
149
  puts "Restored #{name_of(path)} from #{path}"
@@ -21,10 +21,13 @@ module Badline
21
21
  @failed = false
22
22
  end
23
23
 
24
- # What's wrong with the events, the first key or menu page that
25
- # names nothing, or an empty string.
26
- def self.error(events)
27
- event = events.find { |candidate| candidate.action == "key" && !key?(candidate.argument) }
24
+ # The C128's two locking keys, outside its matrix.
25
+ LOCKS = %w[caps_lock forty_eighty].freeze
26
+
27
+ # What's wrong with the events for the family's machine, the first
28
+ # key or menu page that names nothing, or an empty string.
29
+ def self.error(events, family)
30
+ event = events.find { |candidate| candidate.action == "key" && !key?(candidate.argument, family) }
28
31
  return "no such key: #{event.argument}" unless event.nil?
29
32
 
30
33
  event = events.find { |candidate| candidate.action == "menu" && !page?(candidate.argument) }
@@ -35,19 +38,24 @@ module Badline
35
38
  # sound. An empty one is its first.
36
39
  def self.page?(name) = name.empty? || PauseMenu::PAGES.any? { |page| page.to_s == name }
37
40
 
38
- def self.key?(name) = name == "restore" || !keyboard_key(name).nil? || !joystick_direction(name).nil?
41
+ def self.key?(name, family)
42
+ name == "restore" || (family == :c128 && LOCKS.include?(name)) ||
43
+ !keyboard_key(name, family).nil? || !joystick_direction(name).nil?
44
+ end
39
45
 
40
- # The Keyboard's key for a name such as space, return or a.
41
- def self.keyboard_key(name)
42
- key = Keyboard.new.matrix.flatten.find { |candidate| candidate.to_s == name }
43
- key.nil? ? Keyboard::COMBINATIONS.keys.find { |candidate| candidate.to_s == name } : key
46
+ # The family's Keyboard key for a name such as space, return or a,
47
+ # or on the C128 esc or keypad1.
48
+ def self.keyboard_key(name, family)
49
+ matrix = family == :c128 ? C128::KEYBOARD_MATRIX : Input::Keyboard::C64_MATRIX
50
+ key = matrix.flatten.find { |candidate| candidate.to_s == name }
51
+ key.nil? ? Input::Keyboard::COMBINATIONS.keys.find { |candidate| candidate.to_s == name } : key
44
52
  end
45
53
 
46
54
  # The direction of joy1-NAME or joy2-NAME, such as up or fire.
47
55
  def self.joystick_direction(name)
48
56
  return nil unless name.start_with?("joy1-") || name.start_with?("joy2-")
49
57
 
50
- Joystick::DIRECTIONS.keys.find { |direction| direction.to_s == name[5, name.size - 5] }
58
+ Input::Joystick::DIRECTIONS.keys.find { |direction| direction.to_s == name[5, name.size - 5] }
51
59
  end
52
60
 
53
61
  # The screenshot's file name, with the frame's number in place of %d,
@@ -130,6 +138,8 @@ module Badline
130
138
 
131
139
  def press(computer, event)
132
140
  name = event.argument
141
+ return lock(computer, name) if LOCKS.include?(name)
142
+
133
143
  if name == "restore"
134
144
  computer.press_restore
135
145
  return hold(event)
@@ -137,13 +147,21 @@ module Badline
137
147
 
138
148
  direction = Timeline.joystick_direction(name)
139
149
  if direction.nil?
140
- computer.keyboard.press(Timeline.keyboard_key(name))
150
+ computer.keyboard.press(Timeline.keyboard_key(name, computer.family))
141
151
  else
142
152
  joystick(computer, name).press(direction)
143
153
  end
144
154
  hold(event)
145
155
  end
146
156
 
157
+ # CAPS LOCK and 40/80 DISPLAY lock: a push puts one down if it was up,
158
+ # and up if it was down.
159
+ def lock(computer, name)
160
+ return unless computer.family == :c128
161
+
162
+ name == "caps_lock" ? computer.push_caps_lock : computer.push_display_key
163
+ end
164
+
147
165
  # The VIC-20 and the C128 have no cartridge with a button yet.
148
166
  def freeze(computer, event)
149
167
  return unless computer.family == :c64
@@ -164,7 +182,7 @@ module Badline
164
182
 
165
183
  direction = Timeline.joystick_direction(name)
166
184
  if direction.nil?
167
- computer.keyboard.release(Timeline.keyboard_key(name))
185
+ computer.keyboard.release(Timeline.keyboard_key(name, computer.family))
168
186
  else
169
187
  joystick(computer, name).release(direction)
170
188
  end
@@ -0,0 +1,78 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Badline
4
+ module Input
5
+ # CIA 1's control ports and the keyboard matrix that spans them.
6
+ #
7
+ # Port A carries the matrix rows together with joystick 2, port B the matrix
8
+ # columns together with joystick 1. Every switch is active low, so the
9
+ # joysticks are wired-AND onto the lines *before* the matrix settles: a stick
10
+ # held in a direction selects rows and columns of its own, which is where
11
+ # hardware's phantom keypresses come from. Paddles and a 1351 mouse have
12
+ # buttons on those same lines.
13
+ #
14
+ # The ports also carry SID's POTX/POTY lines, which reach them through analog
15
+ # switches closed by CIA 1 PA6 (port 1) and PA7 (port 2). Close both and the
16
+ # pots sit in parallel, so SID charges through the lower of the two
17
+ # resistances; close neither and the lines float high. Reading a pot asks the
18
+ # CIA for the current select lines, since a program sets them and then goes
19
+ # straight to SID without touching the ports again.
20
+ #
21
+ # A matrix may have rows beyond port A's eight. Their select lines come from
22
+ # outside the CIA and are pushed into +extra_rows+, bit 0 for the ninth row
23
+ # upwards, active low like port A; idle, every line is high and none of
24
+ # those rows is selected. They settle with port A's lines as rows 8 and up,
25
+ # so their keys reach port B, but port A reads only its own eight.
26
+ class ControlPorts
27
+ PORT1_POTS = 0x40
28
+ PORT2_POTS = 0x80
29
+ FIRE = 0x10
30
+
31
+ attr_reader :keyboard, :joystick1, :joystick2
32
+ attr_accessor :device1, :device2, :port_a_source, :extra_rows
33
+
34
+ def initialize(keyboard:, joystick1:, joystick2:)
35
+ @keyboard = keyboard
36
+ @joystick1 = joystick1
37
+ @joystick2 = joystick2
38
+ @device1 = nil
39
+ @device2 = nil
40
+ @port_a_source = nil
41
+ @extra_rows = 0xff
42
+ end
43
+
44
+ def read_a(port_a, port_b) = scan(port_a, port_b).first & 0xff
45
+
46
+ def read_b(port_a, port_b) = scan(port_a, port_b).last
47
+
48
+ # Control port 1 pin 6 is both the port B fire line and the VIC's light
49
+ # pen input, so joystick 1's button and a 1351's left button latch
50
+ # $D013/$D014. The CIA samples this every cycle, so it skips the matrix and
51
+ # asks only what the port's own devices drive.
52
+ def port_b4_high? = port_bits(@joystick1, @device1).anybits?(FIRE)
53
+
54
+ def pot_x = selected_devices.map(&:pot_x).min || 0xff
55
+
56
+ def pot_y = selected_devices.map(&:pot_y).min || 0xff
57
+
58
+ private
59
+
60
+ def selected_devices
61
+ lines = @port_a_source&.port_a_lines || 0x00
62
+ devices = []
63
+ devices << @device1 if @device1 && lines.anybits?(PORT1_POTS)
64
+ devices << @device2 if @device2 && lines.anybits?(PORT2_POTS)
65
+ devices
66
+ end
67
+
68
+ def scan(port_a, port_b)
69
+ keyboard.scan((port_a & port_bits(@joystick2, @device2)) | (@extra_rows << 8),
70
+ port_b & port_bits(@joystick1, @device1))
71
+ end
72
+
73
+ def port_bits(joystick, device)
74
+ device ? joystick.port_bits & device.port_bits : joystick.port_bits
75
+ end
76
+ end
77
+ end
78
+ end
@@ -0,0 +1,27 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Badline
4
+ module Input
5
+ class Joystick
6
+ DIRECTIONS = { up: 0, down: 1, left: 2, right: 3, fire: 4 }.freeze
7
+
8
+ # Kept up to date as switches move, since the CIA samples the fire line
9
+ # every cycle to drive the light pen input.
10
+ attr_reader :port_bits
11
+
12
+ def initialize
13
+ @port_bits = 0xff
14
+ end
15
+
16
+ def press(direction)
17
+ bit = DIRECTIONS[direction]
18
+ @port_bits &= ~(1 << bit) if bit
19
+ end
20
+
21
+ def release(direction)
22
+ bit = DIRECTIONS[direction]
23
+ @port_bits |= 1 << bit if bit
24
+ end
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,114 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Badline
4
+ module Input
5
+ # The key matrix spanning CIA 1's two ports: rows on port A, columns on
6
+ # port B. Row lines beyond the eighth sit above port A's bits in +rows+.
7
+ #
8
+ # A pressed key shorts its row line to its column line, so whichever side is
9
+ # pulled low drags the other down with it. Scanning works in either
10
+ # direction, and chains of keys sharing a line propagate the pulldown -- the
11
+ # ghost keys real hardware reports.
12
+ class Keyboard
13
+ include IntegerHelper
14
+
15
+ # Keys the C64 types as a combination: cursor up and cursor left are the
16
+ # shifted cursor keys. Pressing one holds both of its matrix keys, while
17
+ # a shift key held on its own stays down when it is released.
18
+ COMBINATIONS = {
19
+ cursor_up: %i[lshift cursor_v],
20
+ cursor_left: %i[lshift cursor_h]
21
+ }.freeze
22
+
23
+ # The C64's wiring: one row of keys per port A line, in port B column
24
+ # order.
25
+ C64_MATRIX = [
26
+ %i[delete return cursor_h f7 f1 f3 f5 cursor_v],
27
+ %i[3 w a 4 z s e lshift],
28
+ %i[5 r d 6 c f t x],
29
+ %i[7 y g 8 b h u v],
30
+ %i[9 i j 0 m k o n],
31
+ %i[+ p l - . : @ ,],
32
+ %i[£ * ; clr_home rshift = up /],
33
+ %i[1 left control 2 space cbm q run_stop]
34
+ ].freeze
35
+
36
+ attr_reader :keys, :matrix
37
+
38
+ def initialize(matrix: C64_MATRIX)
39
+ @keys = []
40
+ @row_masks = nil
41
+ @matrix = matrix
42
+ @connected = true
43
+ end
44
+
45
+ # Whether the machine has a keyboard. The C64GS has none: no key goes
46
+ # down on it.
47
+ def connected? = @connected
48
+
49
+ def connected=(connected)
50
+ @connected = connected
51
+ @keys.clear unless connected
52
+ @row_masks = nil
53
+ end
54
+
55
+ def press(key)
56
+ return unless @connected && valid_key?(key)
57
+
58
+ @keys << key
59
+ @row_masks = nil
60
+ end
61
+
62
+ def release(key)
63
+ @keys.reject! { |k| k == key }
64
+ @row_masks = nil
65
+ end
66
+
67
+ # Settles the row (port A) and column (port B) lines against each other
68
+ # until no key contact pulls a line low any more, and returns both.
69
+ def scan(rows, cols)
70
+ return [rows, cols] if keys.empty?
71
+
72
+ loop do
73
+ settled_rows = rows
74
+ settled_cols = cols
75
+
76
+ row_masks.each_with_index do |mask, row|
77
+ bit = 1 << row
78
+ next if mask.zero? || (rows.allbits?(bit) && cols.allbits?(mask))
79
+
80
+ rows &= ~bit
81
+ cols &= ~mask
82
+ end
83
+
84
+ return [rows, cols] if rows == settled_rows && cols == settled_cols
85
+ end
86
+ end
87
+
88
+ def read_a(port_a, port_b) = scan(port_a, port_b).first
89
+
90
+ def read_b(port_a, port_b) = scan(port_a, port_b).last
91
+
92
+ private
93
+
94
+ # Columns held down per row, as a bit mask of port B lines.
95
+ def row_masks
96
+ @row_masks ||= begin
97
+ pressed = held
98
+ matrix.map do |row|
99
+ row.each_with_index.sum { |key, column| pressed.include?(key) ? 1 << column : 0 }
100
+ end
101
+ end
102
+ end
103
+
104
+ # The matrix keys held down, with each combination expanded.
105
+ def held
106
+ keys.flat_map { |key| COMBINATIONS[key] || [key] }
107
+ end
108
+
109
+ def valid_key?(key)
110
+ COMBINATIONS.key?(key) || matrix.flatten.include?(key)
111
+ end
112
+ end
113
+ end
114
+ end
@@ -0,0 +1,98 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Badline
4
+ module Input
5
+ # Types PETSCII codes on a Keyboard's matrix, one key at a time, for
6
+ # software that scans the keys itself instead of reading a KERNAL's
7
+ # keyboard buffer: each code's keys go down for HOLD cycles, then up for
8
+ # GAP cycles. Codes it has no key for are skipped.
9
+ class MatrixTypist
10
+ HOLD = 100_000
11
+ GAP = 40_000
12
+
13
+ # The keys typed without SHIFT, by PETSCII code.
14
+ PLAIN = {
15
+ 0x0d => :return, 0x20 => :space, 0x2a => :*, 0x2b => :+, 0x2c => :",", 0x2d => :-, 0x2e => :".",
16
+ 0x2f => :/, 0x3a => :":", 0x3b => :";", 0x3d => :"=", 0x40 => :"@"
17
+ }.freeze
18
+
19
+ # The keys typed with SHIFT, by PETSCII code.
20
+ SHIFTED = {
21
+ 0x21 => :"1", 0x22 => :"2", 0x23 => :"3", 0x24 => :"4", 0x25 => :"5", 0x26 => :"6", 0x27 => :"7",
22
+ 0x28 => :"8", 0x29 => :"9", 0x3c => :",", 0x3e => :".", 0x3f => :/, 0x5b => :":", 0x5d => :";"
23
+ }.freeze
24
+
25
+ DIGITS = %i[0 1 2 3 4 5 6 7 8 9].freeze
26
+ LETTERS = %i[a b c d e f g h i j k l m n o p q r s t u v w x y z].freeze
27
+
28
+ # The keys that type the PETSCII code, SHIFT first, or none.
29
+ def self.keys(code)
30
+ return [DIGITS[code - 0x30]] if code.between?(0x30, 0x39)
31
+ return [LETTERS[code - 0x41]] if code.between?(0x41, 0x5a)
32
+ return [:lshift, LETTERS[code - 0xc1]] if code.between?(0xc1, 0xda)
33
+ return [PLAIN[code]] if PLAIN.key?(code)
34
+ return [:lshift, SHIFTED[code]] if SHIFTED.key?(code)
35
+
36
+ []
37
+ end
38
+
39
+ def initialize(keyboard)
40
+ @keyboard = keyboard
41
+ @held = []
42
+ @code = 0
43
+ @wait = 0
44
+ @typing = false
45
+ end
46
+
47
+ # Whether it is part way through codes: from the first key down until
48
+ # the last one is up and its gap has passed.
49
+ def typing? = @typing
50
+
51
+ # A cycle of typing +codes+, taking each code off its front as its keys
52
+ # go down.
53
+ def cycle!(codes)
54
+ @wait -= 1 if @wait.positive?
55
+ return unless @wait.zero?
56
+
57
+ if @held.empty?
58
+ press(codes)
59
+ else
60
+ @held.each { |key| @keyboard.release(key) }
61
+ @held = []
62
+ @wait = GAP
63
+ end
64
+ end
65
+
66
+ # Where it is in its typing, in a keyed snapshot module under `prefix`:
67
+ # the code whose keys are down, if any, the cycles left before they
68
+ # go up or the next code's go down, and whether it is part way
69
+ # through. The keys go down again on load.
70
+ def save_fields(out, prefix)
71
+ out.optional_int("#{prefix}code", @held.empty? ? nil : @code)
72
+ out.int("#{prefix}wait", @wait).bool("#{prefix}typing", @typing)
73
+ end
74
+
75
+ def load_fields(input, prefix)
76
+ @held.each { |key| @keyboard.release(key) }
77
+ code = input.optional_int("#{prefix}code")
78
+ @code = code || 0
79
+ @held = code.nil? ? [] : MatrixTypist.keys(code)
80
+ @held.each { |key| @keyboard.press(key) }
81
+ @wait = input.int("#{prefix}wait", @wait)
82
+ @typing = input.bool("#{prefix}typing", @typing)
83
+ end
84
+
85
+ private
86
+
87
+ def press(codes)
88
+ @typing = !codes.empty?
89
+ return unless @typing
90
+
91
+ @code = codes.shift
92
+ @held = MatrixTypist.keys(@code)
93
+ @held.each { |key| @keyboard.press(key) }
94
+ @wait = @held.empty? ? 1 : HOLD
95
+ end
96
+ end
97
+ end
98
+ end
data/lib/badline/input.rb CHANGED
@@ -1,4 +1,8 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require "badline/input/keyboard"
4
+ require "badline/input/joystick"
5
+ require "badline/input/control_ports"
3
6
  require "badline/input/mouse1351"
4
7
  require "badline/input/paddles"
8
+ require "badline/input/matrix_typist"
@@ -0,0 +1,45 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Badline
4
+ module KernalTrap
5
+ # Observe-only PC trap on the KERNAL CHROUT routine ($FFD2). Records each
6
+ # character written while the layout finds the KERNAL mapped in, then lets execution fall
7
+ # through to the ROM, so the screen output is unaffected. Used for headless
8
+ # capture of program output.
9
+ class Chrout
10
+ ADDRESS = 0xffd2
11
+
12
+ attr_reader :output
13
+
14
+ # The KERNAL layout whose mapping the trap checks.
15
+ attr_writer :layout
16
+
17
+ def initialize(cpu:, bus:, layout:)
18
+ @cpu = cpu
19
+ @bus = bus
20
+ @layout = layout
21
+ @output = +""
22
+ end
23
+
24
+ def call
25
+ @output << ascii(@cpu.a) if @layout.kernal_mapped?(@bus)
26
+ end
27
+
28
+ def inspect
29
+ "#<#{self.class.name} output=#{@output.inspect}>"
30
+ end
31
+
32
+ private
33
+
34
+ def ascii(byte)
35
+ case byte
36
+ when 0x0d then "\n"
37
+ when 0x41..0x5a then (byte + 0x20).chr
38
+ when 0xc1..0xda then (byte - 0x80).chr
39
+ when 0x20..0x7e then byte.chr
40
+ else ""
41
+ end
42
+ end
43
+ end
44
+ end
45
+ end
@@ -2,7 +2,7 @@
2
2
 
3
3
  module Badline
4
4
  module KernalTrap
5
- class Drive
5
+ class DOS
6
6
  # The commands that read and write a block through a buffer channel,
7
7
  # and B-P, which moves its pointer.
8
8
  module BlockCommands
@@ -0,0 +1,156 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Badline
4
+ module KernalTrap
5
+ class DOS
6
+ # One open channel on the virtual drive, holding a file, a block buffer
7
+ # or the command channel's status message. It gives out one byte at a
8
+ # time and flags the last one, so the bus can raise EOI with it. A
9
+ # channel cut short by a read error never flags one, and holds the
10
+ # error for the drive to report once its bytes run out.
11
+ class Channel
12
+ attr_accessor :buffer
13
+ attr_reader :error, :pointer
14
+ attr_writer :lead
15
+
16
+ def initialize(bytes = [], error: nil, writable: false, base: 0)
17
+ @base = base
18
+ replace(bytes)
19
+ @error = error
20
+ @writable = writable
21
+ end
22
+
23
+ # A block buffer opened with "#", which reads and writes one of the
24
+ # drive's buffers in its RAM, so M-R and M-W reach the same bytes. It
25
+ # hands out the whole block unless a B-R sets a shorter end, and
26
+ # flags that byte as the last. The DOS opens it with the pointer at 1
27
+ # and the buffer's number as the byte to send, so the first read
28
+ # answers with the number and the next one moves on to the third
29
+ # byte.
30
+ def self.buffer(memory, number)
31
+ new(memory.ram, writable: true, base: Memory.buffer_address(number)).tap do |channel|
32
+ channel.buffer = number
33
+ channel.rewind(BLOCK_SIZE)
34
+ channel.pointer = 1
35
+ channel.lead = number
36
+ end
37
+ end
38
+
39
+ # A file's bytes, up to the first block its chain can't read. The
40
+ # drive reads a block ahead before sending the last byte of the one
41
+ # before, so a bad block holds that byte back too, and a bad first
42
+ # block or a missing file leaves nothing to send.
43
+ def self.for_file(storage, name, type)
44
+ data = storage.read_file(name, type:)
45
+ return new([], error: [FILE_NOT_FOUND]) unless data
46
+
47
+ failure = storage.respond_to?(:read_error) && storage.read_error(name, type:)
48
+ return new(data) unless failure
49
+
50
+ new(data[0, failure[:offset] - 1] || [], error: failure.values_at(:error, :track, :sector))
51
+ end
52
+
53
+ # The file an open names. The drive keeps the first block of the file
54
+ # a LOAD (secondary address 0) opens at $7E and $026F of its memory,
55
+ # and a LOAD of a name starting with "*" reopens that file rather than
56
+ # the first one on the disk. Loaders write the block there with M-W
57
+ # to load a file the directory doesn't list. A LOAD of "$" reads the
58
+ # directory as a BASIC program, which a pattern after a colon narrows
59
+ # to the files it names.
60
+ def self.for_name(storage, memory, secondary, name, type)
61
+ return new(storage.directory.bytes(name)) if listing?(storage, secondary, name)
62
+
63
+ file = Storage.parse_name(name).first
64
+ return for_file(storage, file, type) unless secondary.zero? && storage.respond_to?(:first_block)
65
+
66
+ last = memory.last_program if name.start_with?("*")
67
+ return at_block(storage, *last) if last
68
+
69
+ block = storage.first_block(file, type:)
70
+ memory.last_program = block if block
71
+ for_file(storage, file, type)
72
+ end
73
+
74
+ def self.listing?(storage, secondary, name)
75
+ secondary.zero? && name.start_with?("$") && storage.respond_to?(:directory)
76
+ end
77
+
78
+ # The file whose chain starts at the block.
79
+ def self.at_block(storage, track, sector)
80
+ data = storage.read_file_at(track, sector)
81
+ data ? new(data) : new([], error: [ILLEGAL_TRACK_OR_SECTOR, track, sector])
82
+ end
83
+
84
+ def replace(bytes, length = bytes.length)
85
+ @bytes = bytes
86
+ rewind(length)
87
+ end
88
+
89
+ def rewind(length)
90
+ @length = length
91
+ self.pointer = 0
92
+ end
93
+
94
+ def pointer=(position)
95
+ @pointer = position
96
+ @lead = nil
97
+ end
98
+
99
+ # A block read into the channel. A buffer channel takes it into its
100
+ # buffer, a file channel hands it out in place of the file.
101
+ def load(block, length)
102
+ return replace(block.dup, length) unless @writable
103
+
104
+ @bytes[@base, block.length] = block
105
+ rewind(length)
106
+ end
107
+
108
+ def writable? = @writable
109
+
110
+ # A buffer channel's block, as a block write puts it on the disk.
111
+ def block = @bytes[@base, BLOCK_SIZE]
112
+
113
+ def exhausted?
114
+ @pointer >= @length
115
+ end
116
+
117
+ # A file that holds nothing but its error never opened.
118
+ def failed?
119
+ exhausted? && !@error.nil?
120
+ end
121
+
122
+ # Each byte lands at the pointer, which wraps within the block.
123
+ def write(bytes)
124
+ @lead = nil
125
+ bytes.each do |byte|
126
+ @bytes[@base + @pointer] = byte
127
+ @pointer = (@pointer + 1) & 0xff
128
+ end
129
+ end
130
+
131
+ def read
132
+ return read_buffer if @writable
133
+ return if exhausted?
134
+
135
+ byte = @lead || @bytes[@base + @pointer]
136
+ @lead = nil
137
+ @pointer += 1
138
+ [byte, exhausted? && !@error]
139
+ end
140
+
141
+ private
142
+
143
+ # A buffer never runs out. Past its last byte, which goes out with
144
+ # EOI, the pointer wraps back to the second, and a pointer set past
145
+ # the end runs on around the block to it.
146
+ def read_buffer
147
+ byte = @lead || @bytes[@base + @pointer]
148
+ @lead = nil
149
+ last = @pointer == @length - 1
150
+ @pointer = last ? 1 : (@pointer + 1) & 0xff
151
+ [byte, last]
152
+ end
153
+ end
154
+ end
155
+ end
156
+ end