badline 0.12.1 → 0.13.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 (505) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +17 -0
  3. data/CLAUDE.md +59 -31
  4. data/README.md +1 -1
  5. data/doc/library.md +9 -9
  6. data/doc/pinned-behaviour.md +105 -105
  7. data/doc/snapshots.md +1 -4
  8. data/lib/badline/audio/bare_player.rb +3 -3
  9. data/lib/badline/audio/cli.rb +6 -6
  10. data/lib/badline/audio/jukebox.rb +2 -2
  11. data/lib/badline/audio/machine_player.rb +2 -2
  12. data/lib/badline/{media → audio}/queue.rb +1 -1
  13. data/lib/badline/audio/queued_tune.rb +6 -6
  14. data/lib/badline/audio/renderer.rb +5 -5
  15. data/lib/badline/audio/stereo.rb +1 -1
  16. data/lib/badline/c128/{bus_state.rb → bus/saved_state.rb} +1 -1
  17. data/lib/badline/c128/bus.rb +18 -17
  18. data/lib/badline/c128/color_lines.rb +2 -2
  19. data/lib/badline/c128/cpu.rb +1 -1
  20. data/lib/badline/c128/drives.rb +10 -11
  21. data/lib/badline/c128/fast_serial.rb +5 -5
  22. data/lib/badline/c128/mmu_pages.rb +1 -1
  23. data/lib/badline/c128/modes.rb +2 -2
  24. data/lib/badline/c128/saved_state.rb +21 -47
  25. data/lib/badline/c128/z80.rb +2 -2
  26. data/lib/badline/c128.rb +14 -9
  27. data/lib/badline/{computer → c64}/attachments.rb +4 -6
  28. data/lib/badline/c64/banked_ram/banking.rb +32 -0
  29. data/lib/badline/c64/banked_ram/plus256k.rb +56 -0
  30. data/lib/badline/c64/banked_ram/plus60k.rb +45 -0
  31. data/lib/badline/c64/banked_ram/unexpanded.rb +30 -0
  32. data/lib/badline/c64/banked_ram.rb +23 -0
  33. data/lib/badline/c64/bus/extra_sids.rb +32 -0
  34. data/lib/badline/c64/bus/fittings.rb +40 -0
  35. data/lib/badline/c64/bus/pla.rb +149 -0
  36. data/lib/badline/c64/bus/roms.rb +41 -0
  37. data/lib/badline/c64/bus/saved_state.rb +68 -0
  38. data/lib/badline/c64/bus/sid_slots.rb +44 -0
  39. data/lib/badline/c64/bus.rb +218 -0
  40. data/lib/badline/c64/cartridge/action_replay.rb +128 -0
  41. data/lib/badline/c64/cartridge/atomic_power.rb +26 -0
  42. data/lib/badline/c64/cartridge/bank.rb +83 -0
  43. data/lib/badline/c64/cartridge/comal80.rb +52 -0
  44. data/lib/badline/c64/cartridge/dinamic.rb +36 -0
  45. data/lib/badline/c64/cartridge/easy_flash.rb +147 -0
  46. data/lib/badline/c64/cartridge/epyx_fastload.rb +81 -0
  47. data/lib/badline/c64/cartridge/final_cartridge3.rb +91 -0
  48. data/lib/badline/c64/cartridge/flash/saved_state.rb +40 -0
  49. data/lib/badline/c64/cartridge/flash.rb +279 -0
  50. data/lib/badline/c64/cartridge/freezer.rb +35 -0
  51. data/lib/badline/c64/cartridge/fun_play.rb +40 -0
  52. data/lib/badline/c64/cartridge/g_mod2.rb +76 -0
  53. data/lib/badline/c64/cartridge/game_system.rb +42 -0
  54. data/lib/badline/c64/cartridge/geo_ram.rb +81 -0
  55. data/lib/badline/c64/cartridge/kcs_power.rb +77 -0
  56. data/lib/badline/c64/cartridge/mach5.rb +34 -0
  57. data/lib/badline/c64/cartridge/magic_desk.rb +32 -0
  58. data/lib/badline/c64/cartridge/ocean.rb +37 -0
  59. data/lib/badline/c64/cartridge/pagefox.rb +70 -0
  60. data/lib/badline/c64/cartridge/retro_replay/saved_state.rb +47 -0
  61. data/lib/badline/c64/cartridge/retro_replay.rb +286 -0
  62. data/lib/badline/c64/cartridge/rex_utility.rb +31 -0
  63. data/lib/badline/c64/cartridge/rgcd.rb +60 -0
  64. data/lib/badline/c64/cartridge/saved_state.rb +63 -0
  65. data/lib/badline/c64/cartridge/simons_basic.rb +37 -0
  66. data/lib/badline/c64/cartridge/standard.rb +17 -0
  67. data/lib/badline/c64/cartridge/super_games.rb +50 -0
  68. data/lib/badline/c64/cartridge/westermann.rb +32 -0
  69. data/lib/badline/c64/cartridge/zaxxon.rb +68 -0
  70. data/lib/badline/c64/cartridge.rb +258 -0
  71. data/lib/badline/c64/color_memory.rb +27 -0
  72. data/lib/badline/{computer → c64}/common.rb +2 -2
  73. data/lib/badline/{computer → c64}/kernal_traps.rb +18 -18
  74. data/lib/badline/c64/reu/dma/saved_state.rb +52 -0
  75. data/lib/badline/c64/reu/dma.rb +275 -0
  76. data/lib/badline/c64/reu/ram.rb +109 -0
  77. data/lib/badline/c64/reu/saved_state.rb +55 -0
  78. data/lib/badline/c64/reu/trigger.rb +34 -0
  79. data/lib/badline/c64/reu.rb +291 -0
  80. data/lib/badline/{computer → c64}/saved_state.rb +13 -18
  81. data/lib/badline/{computer → c64}/true_drives.rb +11 -11
  82. data/lib/badline/{computer.rb → c64.rb} +18 -13
  83. data/lib/badline/chips/cia/fast_forward.rb +62 -0
  84. data/lib/badline/chips/cia/interrupt_register.rb +145 -0
  85. data/lib/badline/chips/cia/saved_state.rb +49 -0
  86. data/lib/badline/chips/cia/serial/saved_state.rb +45 -0
  87. data/lib/badline/chips/cia/serial.rb +250 -0
  88. data/lib/badline/chips/cia/time_of_day.rb +232 -0
  89. data/lib/badline/chips/cia/timer.rb +208 -0
  90. data/lib/badline/chips/cia.rb +281 -0
  91. data/lib/badline/chips/cpu/addressing.rb +133 -0
  92. data/lib/badline/chips/cpu/boundary.rb +56 -0
  93. data/lib/badline/chips/cpu/core.rb +167 -0
  94. data/lib/badline/chips/cpu/instruction.rb +338 -0
  95. data/lib/badline/chips/cpu/instruction_set/arithmetic.rb +118 -0
  96. data/lib/badline/chips/cpu/instruction_set/bitwise.rb +130 -0
  97. data/lib/badline/chips/cpu/instruction_set/branch.rb +83 -0
  98. data/lib/badline/chips/cpu/instruction_set/flag.rb +67 -0
  99. data/lib/badline/chips/cpu/instruction_set/illegal.rb +274 -0
  100. data/lib/badline/chips/cpu/instruction_set/inc_dec.rb +73 -0
  101. data/lib/badline/chips/cpu/instruction_set/stack.rb +56 -0
  102. data/lib/badline/chips/cpu/instruction_set/transfer.rb +141 -0
  103. data/lib/badline/chips/cpu/instruction_set.rb +59 -0
  104. data/lib/badline/chips/cpu/interrupts.rb +108 -0
  105. data/lib/badline/chips/cpu/microcode.rb +179 -0
  106. data/lib/badline/chips/cpu/operations.rb +90 -0
  107. data/lib/badline/chips/cpu/port.rb +116 -0
  108. data/lib/badline/chips/cpu/saved_state.rb +117 -0
  109. data/lib/badline/chips/cpu/stack_operations.rb +84 -0
  110. data/lib/badline/chips/cpu/traps.rb +23 -0
  111. data/lib/badline/chips/cpu.rb +12 -0
  112. data/lib/badline/chips/sid/catch_up.rb +122 -0
  113. data/lib/badline/chips/sid/decimator.rb +66 -0
  114. data/lib/badline/chips/sid/envelope/saved_state.rb +70 -0
  115. data/lib/badline/chips/sid/envelope.rb +241 -0
  116. data/lib/badline/chips/sid/filter/saved_state.rb +42 -0
  117. data/lib/badline/chips/sid/filter.rb +241 -0
  118. data/lib/badline/chips/sid/saved_state.rb +52 -0
  119. data/lib/badline/chips/sid/voice.rb +79 -0
  120. data/lib/badline/chips/sid/waveform/combined.rb +128 -0
  121. data/lib/badline/chips/sid/waveform/fast_forward.rb +107 -0
  122. data/lib/badline/chips/sid/waveform/noise_writeback.rb +124 -0
  123. data/lib/badline/chips/sid/waveform/saved_state.rb +68 -0
  124. data/lib/badline/chips/sid/waveform.rb +307 -0
  125. data/lib/badline/chips/sid.rb +265 -0
  126. data/lib/badline/{c128 → chips}/vdc/memory.rb +1 -1
  127. data/lib/badline/{c128 → chips}/vdc/painter.rb +1 -1
  128. data/lib/badline/{c128 → chips}/vdc/raster.rb +1 -1
  129. data/lib/badline/chips/vdc/saved_state.rb +36 -0
  130. data/lib/badline/{c128 → chips}/vdc/window.rb +1 -1
  131. data/lib/badline/{c128 → chips}/vdc.rb +7 -31
  132. data/lib/badline/chips/via/control_lines.rb +124 -0
  133. data/lib/badline/chips/via/fast_forward.rb +55 -0
  134. data/lib/badline/chips/via/interrupt_register.rb +69 -0
  135. data/lib/badline/chips/via/saved_state.rb +44 -0
  136. data/lib/badline/chips/via/shift_register.rb +182 -0
  137. data/lib/badline/chips/via/timer1.rb +133 -0
  138. data/lib/badline/chips/via/timer2.rb +135 -0
  139. data/lib/badline/chips/via.rb +268 -0
  140. data/lib/badline/chips/vic/bank.rb +135 -0
  141. data/lib/badline/chips/vic/border_mask.rb +88 -0
  142. data/lib/badline/chips/vic/collisions.rb +117 -0
  143. data/lib/badline/chips/vic/color_patches.rb +49 -0
  144. data/lib/badline/chips/vic/display_state.rb +187 -0
  145. data/lib/badline/chips/vic/graphics_mode.rb +166 -0
  146. data/lib/badline/chips/vic/graphics_shifter/saved_state.rb +40 -0
  147. data/lib/badline/chips/vic/graphics_shifter.rb +187 -0
  148. data/lib/badline/chips/vic/register_log.rb +83 -0
  149. data/lib/badline/chips/vic/registers.rb +197 -0
  150. data/lib/badline/chips/vic/saved_state.rb +82 -0
  151. data/lib/badline/chips/vic/sequencer/saved_state.rb +91 -0
  152. data/lib/badline/chips/vic/sequencer.rb +277 -0
  153. data/lib/badline/chips/vic/sequencer_output.rb +118 -0
  154. data/lib/badline/chips/vic/sprite/internal_bus.rb +70 -0
  155. data/lib/badline/chips/vic/sprite/saved_state.rb +66 -0
  156. data/lib/badline/chips/vic/sprite/shifter.rb +148 -0
  157. data/lib/badline/chips/vic/sprite/timing.rb +58 -0
  158. data/lib/badline/chips/vic/sprite.rb +339 -0
  159. data/lib/badline/chips/vic/sprites/saved_state.rb +59 -0
  160. data/lib/badline/chips/vic/sprites.rb +284 -0
  161. data/lib/badline/chips/vic.rb +783 -0
  162. data/lib/badline/chips/vic_i/painter/saved_state.rb +40 -0
  163. data/lib/badline/{vic20/vic → chips/vic_i}/painter.rb +6 -30
  164. data/lib/badline/{vic20/vic → chips/vic_i}/saved_state.rb +2 -2
  165. data/lib/badline/chips/vic_i/sound/output.rb +85 -0
  166. data/lib/badline/chips/vic_i/sound.rb +235 -0
  167. data/lib/badline/{vic20/vic → chips/vic_i}/text_window.rb +2 -2
  168. data/lib/badline/{vic20/vic.rb → chips/vic_i.rb} +5 -5
  169. data/lib/badline/chips/z80/arithmetic.rb +119 -0
  170. data/lib/badline/chips/z80/bit_page.rb +57 -0
  171. data/lib/badline/chips/z80/block_io.rb +60 -0
  172. data/lib/badline/chips/z80/block_transfers.rb +61 -0
  173. data/lib/badline/chips/z80/bus_cycles.rb +113 -0
  174. data/lib/badline/chips/z80/core.rb +115 -0
  175. data/lib/badline/chips/z80/exchanges.rb +52 -0
  176. data/lib/badline/chips/z80/extended_page.rb +91 -0
  177. data/lib/badline/chips/z80/flags.rb +39 -0
  178. data/lib/badline/chips/z80/interrupt_acceptance.rb +48 -0
  179. data/lib/badline/chips/z80/jumps.rb +84 -0
  180. data/lib/badline/chips/z80/loads.rb +100 -0
  181. data/lib/badline/chips/z80/main_page.rb +136 -0
  182. data/lib/badline/chips/z80/registers.rb +122 -0
  183. data/lib/badline/chips/z80/saved_state.rb +71 -0
  184. data/lib/badline/chips/z80/shifts.rb +66 -0
  185. data/lib/badline/chips/z80.rb +13 -0
  186. data/lib/badline/core.rb +26 -28
  187. data/lib/badline/drives/datasette/saved_state.rb +37 -0
  188. data/lib/badline/drives/datasette.rb +140 -0
  189. data/lib/badline/drives/engine/core.rb +149 -0
  190. data/lib/badline/drives/engine/fast_serial.rb +75 -0
  191. data/lib/badline/drives/engine/idle.rb +127 -0
  192. data/lib/badline/drives/engine/idle_cpu.rb +30 -0
  193. data/lib/badline/drives/engine/orbit.rb +81 -0
  194. data/lib/badline/drives/engine/serial_lines.rb +75 -0
  195. data/lib/badline/drives/engine/sleep.rb +114 -0
  196. data/lib/badline/drives/engine/vias.rb +86 -0
  197. data/lib/badline/drives/engine/watch.rb +152 -0
  198. data/lib/badline/drives/engine.rb +11 -0
  199. data/lib/badline/drives/model1541/bus.rb +88 -0
  200. data/lib/badline/drives/model1541/byte_ready.rb +50 -0
  201. data/lib/badline/drives/model1541/cpu.rb +13 -0
  202. data/lib/badline/drives/model1541/disk/saved_state.rb +88 -0
  203. data/lib/badline/drives/model1541/disk.rb +317 -0
  204. data/lib/badline/drives/model1541/disk_via.rb +38 -0
  205. data/lib/badline/drives/model1541/gcr.rb +59 -0
  206. data/lib/badline/drives/model1541/mechanism/saved_state.rb +72 -0
  207. data/lib/badline/drives/model1541/mechanism.rb +315 -0
  208. data/lib/badline/drives/model1541/rotation.rb +123 -0
  209. data/lib/badline/drives/model1541/saved_state.rb +44 -0
  210. data/lib/badline/drives/model1541/sector_reader.rb +78 -0
  211. data/lib/badline/drives/model1541/serial_port.rb +20 -0
  212. data/lib/badline/drives/model1541/track.rb +100 -0
  213. data/lib/badline/drives/model1541.rb +81 -0
  214. data/lib/badline/drives/model1571/bus.rb +113 -0
  215. data/lib/badline/drives/model1571/cpu.rb +13 -0
  216. data/lib/badline/drives/model1571/saved_state.rb +47 -0
  217. data/lib/badline/drives/model1571/serial_port.rb +39 -0
  218. data/lib/badline/drives/model1571/serial_via.rb +27 -0
  219. data/lib/badline/drives/model1571/wd1770.rb +122 -0
  220. data/lib/badline/drives/model1571.rb +142 -0
  221. data/lib/badline/drives/model1581/bus.rb +139 -0
  222. data/lib/badline/drives/model1581/cpu.rb +13 -0
  223. data/lib/badline/drives/model1581/disk/saved_state.rb +89 -0
  224. data/lib/badline/drives/model1581/disk.rb +137 -0
  225. data/lib/badline/drives/model1581/mechanism.rb +119 -0
  226. data/lib/badline/drives/model1581/saved_state.rb +42 -0
  227. data/lib/badline/drives/model1581/serial_port.rb +54 -0
  228. data/lib/badline/drives/model1581/slot.rb +79 -0
  229. data/lib/badline/drives/model1581/track.rb +102 -0
  230. data/lib/badline/drives/model1581/track_bytes.rb +77 -0
  231. data/lib/badline/drives/model1581/wd1772/registers.rb +91 -0
  232. data/lib/badline/drives/model1581/wd1772/saved_state.rb +68 -0
  233. data/lib/badline/drives/model1581/wd1772/sector_writes.rb +75 -0
  234. data/lib/badline/drives/model1581/wd1772/sectors.rb +152 -0
  235. data/lib/badline/drives/model1581/wd1772/stepping.rb +111 -0
  236. data/lib/badline/drives/model1581/wd1772/tracks.rb +127 -0
  237. data/lib/badline/drives/model1581/wd1772/turn.rb +75 -0
  238. data/lib/badline/drives/model1581/wd1772.rb +218 -0
  239. data/lib/badline/drives/model1581.rb +206 -0
  240. data/lib/badline/drives/serial_bus.rb +161 -0
  241. data/lib/badline/frontend/boot.rb +3 -3
  242. data/lib/badline/frontend/machine_window.rb +3 -1
  243. data/lib/badline/frontend/player/filter_view.rb +1 -1
  244. data/lib/badline/frontend/player/player_controller.rb +2 -2
  245. data/lib/badline/frontend/player/player_screen.rb +1 -0
  246. data/lib/badline/frontend/player/theme.rb +7 -7
  247. data/lib/badline/frontend/timeline.rb +3 -3
  248. data/lib/badline/input/control_ports.rb +78 -0
  249. data/lib/badline/input/joystick.rb +27 -0
  250. data/lib/badline/input/keyboard.rb +114 -0
  251. data/lib/badline/input.rb +3 -0
  252. data/lib/badline/kernal_trap/{drive → dos}/block_commands.rb +1 -1
  253. data/lib/badline/kernal_trap/dos/channel.rb +183 -0
  254. data/lib/badline/kernal_trap/{drive → dos}/channels.rb +1 -1
  255. data/lib/badline/kernal_trap/{drive → dos}/memory.rb +1 -1
  256. data/lib/badline/kernal_trap/{drive → dos}/parameters.rb +1 -1
  257. data/lib/badline/kernal_trap/{drive → dos}/status.rb +1 -1
  258. data/lib/badline/kernal_trap/{drive → dos}/write_file.rb +1 -1
  259. data/lib/badline/kernal_trap/{drive → dos}/writes.rb +1 -1
  260. data/lib/badline/kernal_trap/{drive.rb → dos.rb} +11 -10
  261. data/lib/badline/kernal_trap/load.rb +5 -5
  262. data/lib/badline/kernal_trap/save.rb +4 -4
  263. data/lib/badline/kernal_trap/serial.rb +7 -7
  264. data/lib/badline/kernal_trap.rb +1 -2
  265. data/lib/badline/machine/clocking.rb +1 -1
  266. data/lib/badline/machine.rb +5 -5
  267. data/lib/badline/media/true_drive.rb +1 -1
  268. data/lib/badline/media/tune.rb +2 -2
  269. data/lib/badline/media.rb +3 -3
  270. data/lib/badline/model.rb +1 -1
  271. data/lib/badline/roms/README +6 -6
  272. data/lib/badline/sdl.rb +1 -0
  273. data/lib/badline/{storage/sid_file.rb → sid_tune/file.rb} +3 -3
  274. data/lib/badline/{storage → sid_tune}/hvsc.rb +7 -7
  275. data/lib/badline/{storage → sid_tune}/song_lengths.rb +2 -2
  276. data/lib/badline/{storage → sid_tune}/stil/credit.rb +1 -1
  277. data/lib/badline/{storage → sid_tune}/stil.rb +3 -3
  278. data/lib/badline/sid_tune.rb +6 -0
  279. data/lib/badline/snapshot/c128_setup.rb +43 -0
  280. data/lib/badline/snapshot/c64_setup.rb +79 -0
  281. data/lib/badline/snapshot/image.rb +5 -25
  282. data/lib/badline/snapshot/machine_state.rb +1 -1
  283. data/lib/badline/snapshot/setup.rb +22 -66
  284. data/lib/badline/snapshot/state.rb +8 -18
  285. data/lib/badline/snapshot/storage_setup.rb +68 -0
  286. data/lib/badline/snapshot/vic20_setup.rb +37 -0
  287. data/lib/badline/snapshot/vice/c64_mem.rb +1 -1
  288. data/lib/badline/snapshot/vice/restore.rb +1 -1
  289. data/lib/badline/snapshot/vice/reu1764.rb +1 -1
  290. data/lib/badline/snapshot/vice/reu_fields.rb +40 -0
  291. data/lib/badline/snapshot/vice.rb +7 -5
  292. data/lib/badline/snapshot.rb +3 -4
  293. data/lib/badline/storage/d64_image.rb +0 -2
  294. data/lib/badline/storage/d71_image.rb +0 -2
  295. data/lib/badline/storage/d81_image.rb +0 -2
  296. data/lib/badline/storage/disk_image.rb +3 -6
  297. data/lib/badline/storage/g64_image.rb +2 -2
  298. data/lib/badline/storage/host_directory.rb +0 -5
  299. data/lib/badline/storage/t64.rb +2 -8
  300. data/lib/badline/storage/unavailable.rb +1 -1
  301. data/lib/badline/storage.rb +0 -37
  302. data/lib/badline/support/c128_checkpoint.rb +39 -0
  303. data/lib/badline/support/checkpoint.rb +108 -0
  304. data/lib/badline/support/vic20_checkpoint.rb +38 -0
  305. data/lib/badline/support.rb +5 -0
  306. data/lib/badline/version.rb +1 -1
  307. data/lib/badline/vic20/attachments.rb +5 -5
  308. data/lib/badline/vic20/bus/saved_state.rb +35 -0
  309. data/lib/badline/vic20/bus.rb +3 -25
  310. data/lib/badline/vic20/cpu.rb +1 -1
  311. data/lib/badline/vic20/port_wiring.rb +4 -4
  312. data/lib/badline/vic20/saved_state.rb +16 -30
  313. data/lib/badline/vic20/user_via_ports.rb +2 -2
  314. data/lib/badline/vic20.rb +19 -14
  315. metadata +245 -221
  316. data/lib/badline/address_bus/extra_sids.rb +0 -30
  317. data/lib/badline/address_bus/fittings.rb +0 -38
  318. data/lib/badline/address_bus/pla.rb +0 -147
  319. data/lib/badline/address_bus/roms.rb +0 -39
  320. data/lib/badline/address_bus/saved_state.rb +0 -66
  321. data/lib/badline/address_bus/sid_slots.rb +0 -42
  322. data/lib/badline/address_bus.rb +0 -216
  323. data/lib/badline/cartridge/action_replay.rb +0 -126
  324. data/lib/badline/cartridge/atomic_power.rb +0 -24
  325. data/lib/badline/cartridge/bank.rb +0 -81
  326. data/lib/badline/cartridge/comal80.rb +0 -50
  327. data/lib/badline/cartridge/dinamic.rb +0 -34
  328. data/lib/badline/cartridge/easy_flash.rb +0 -145
  329. data/lib/badline/cartridge/epyx_fastload.rb +0 -79
  330. data/lib/badline/cartridge/final_cartridge3.rb +0 -89
  331. data/lib/badline/cartridge/flash.rb +0 -306
  332. data/lib/badline/cartridge/freezer.rb +0 -33
  333. data/lib/badline/cartridge/fun_play.rb +0 -38
  334. data/lib/badline/cartridge/g_mod2.rb +0 -74
  335. data/lib/badline/cartridge/game_system.rb +0 -40
  336. data/lib/badline/cartridge/geo_ram.rb +0 -79
  337. data/lib/badline/cartridge/kcs_power.rb +0 -75
  338. data/lib/badline/cartridge/mach5.rb +0 -32
  339. data/lib/badline/cartridge/magic_desk.rb +0 -30
  340. data/lib/badline/cartridge/ocean.rb +0 -35
  341. data/lib/badline/cartridge/pagefox.rb +0 -68
  342. data/lib/badline/cartridge/retro_replay.rb +0 -320
  343. data/lib/badline/cartridge/rex_utility.rb +0 -29
  344. data/lib/badline/cartridge/rgcd.rb +0 -58
  345. data/lib/badline/cartridge/simons_basic.rb +0 -35
  346. data/lib/badline/cartridge/standard.rb +0 -15
  347. data/lib/badline/cartridge/super_games.rb +0 -48
  348. data/lib/badline/cartridge/westermann.rb +0 -30
  349. data/lib/badline/cartridge/zaxxon.rb +0 -66
  350. data/lib/badline/cartridge.rb +0 -303
  351. data/lib/badline/checkpoint.rb +0 -98
  352. data/lib/badline/cia/fast_forward.rb +0 -60
  353. data/lib/badline/cia/interrupt_register.rb +0 -143
  354. data/lib/badline/cia/serial.rb +0 -275
  355. data/lib/badline/cia/state.rb +0 -47
  356. data/lib/badline/cia/timer.rb +0 -206
  357. data/lib/badline/cia.rb +0 -279
  358. data/lib/badline/color_memory.rb +0 -25
  359. data/lib/badline/control_ports.rb +0 -76
  360. data/lib/badline/cpu/addressing.rb +0 -131
  361. data/lib/badline/cpu/boundary.rb +0 -54
  362. data/lib/badline/cpu/core.rb +0 -165
  363. data/lib/badline/cpu/microcode.rb +0 -177
  364. data/lib/badline/cpu/operations.rb +0 -88
  365. data/lib/badline/cpu/saved_state.rb +0 -115
  366. data/lib/badline/cpu/stack_operations.rb +0 -82
  367. data/lib/badline/cpu.rb +0 -10
  368. data/lib/badline/cpu_port.rb +0 -112
  369. data/lib/badline/datasette.rb +0 -161
  370. data/lib/badline/drive/core.rb +0 -147
  371. data/lib/badline/drive/fast_serial.rb +0 -73
  372. data/lib/badline/drive/idle.rb +0 -125
  373. data/lib/badline/drive/idle_cpu.rb +0 -28
  374. data/lib/badline/drive/orbit.rb +0 -79
  375. data/lib/badline/drive/serial_lines.rb +0 -73
  376. data/lib/badline/drive/sleep.rb +0 -112
  377. data/lib/badline/drive/vias.rb +0 -84
  378. data/lib/badline/drive/watch.rb +0 -150
  379. data/lib/badline/drive.rb +0 -11
  380. data/lib/badline/drive1541/bus.rb +0 -86
  381. data/lib/badline/drive1541/byte_ready.rb +0 -48
  382. data/lib/badline/drive1541/cpu.rb +0 -11
  383. data/lib/badline/drive1541/disk.rb +0 -315
  384. data/lib/badline/drive1541/disk_state.rb +0 -86
  385. data/lib/badline/drive1541/disk_via.rb +0 -36
  386. data/lib/badline/drive1541/gcr.rb +0 -57
  387. data/lib/badline/drive1541/mechanism.rb +0 -313
  388. data/lib/badline/drive1541/mechanism_state.rb +0 -68
  389. data/lib/badline/drive1541/rotation.rb +0 -121
  390. data/lib/badline/drive1541/sector_reader.rb +0 -76
  391. data/lib/badline/drive1541/serial_port.rb +0 -18
  392. data/lib/badline/drive1541/track.rb +0 -98
  393. data/lib/badline/drive1541.rb +0 -111
  394. data/lib/badline/drive1571/bus.rb +0 -111
  395. data/lib/badline/drive1571/cpu.rb +0 -11
  396. data/lib/badline/drive1571/serial_port.rb +0 -37
  397. data/lib/badline/drive1571/serial_via.rb +0 -25
  398. data/lib/badline/drive1571/wd1770.rb +0 -120
  399. data/lib/badline/drive1571.rb +0 -175
  400. data/lib/badline/drive1581/bus.rb +0 -137
  401. data/lib/badline/drive1581/cpu.rb +0 -11
  402. data/lib/badline/drive1581/disk.rb +0 -135
  403. data/lib/badline/drive1581/disk_state.rb +0 -87
  404. data/lib/badline/drive1581/mechanism.rb +0 -117
  405. data/lib/badline/drive1581/serial_port.rb +0 -52
  406. data/lib/badline/drive1581/slot.rb +0 -78
  407. data/lib/badline/drive1581/track.rb +0 -100
  408. data/lib/badline/drive1581/track_bytes.rb +0 -75
  409. data/lib/badline/drive1581/wd1772/registers.rb +0 -89
  410. data/lib/badline/drive1581/wd1772/sector_writes.rb +0 -73
  411. data/lib/badline/drive1581/wd1772/sectors.rb +0 -150
  412. data/lib/badline/drive1581/wd1772/state.rb +0 -66
  413. data/lib/badline/drive1581/wd1772/stepping.rb +0 -109
  414. data/lib/badline/drive1581/wd1772/tracks.rb +0 -125
  415. data/lib/badline/drive1581/wd1772/turn.rb +0 -73
  416. data/lib/badline/drive1581/wd1772.rb +0 -216
  417. data/lib/badline/drive1581.rb +0 -233
  418. data/lib/badline/iec_bus.rb +0 -159
  419. data/lib/badline/instruction.rb +0 -334
  420. data/lib/badline/instruction_set/arithmetic.rb +0 -114
  421. data/lib/badline/instruction_set/bitwise.rb +0 -126
  422. data/lib/badline/instruction_set/branch.rb +0 -79
  423. data/lib/badline/instruction_set/flag.rb +0 -63
  424. data/lib/badline/instruction_set/illegal.rb +0 -270
  425. data/lib/badline/instruction_set/inc_dec.rb +0 -69
  426. data/lib/badline/instruction_set/stack.rb +0 -52
  427. data/lib/badline/instruction_set/transfer.rb +0 -137
  428. data/lib/badline/instruction_set.rb +0 -55
  429. data/lib/badline/interrupts.rb +0 -104
  430. data/lib/badline/joystick.rb +0 -25
  431. data/lib/badline/kernal_trap/channel.rb +0 -181
  432. data/lib/badline/keyboard.rb +0 -112
  433. data/lib/badline/ram_expansion/banking.rb +0 -30
  434. data/lib/badline/ram_expansion/plus256k.rb +0 -54
  435. data/lib/badline/ram_expansion/plus60k.rb +0 -43
  436. data/lib/badline/ram_expansion/unexpanded.rb +0 -28
  437. data/lib/badline/ram_expansion.rb +0 -21
  438. data/lib/badline/reu/dma/saved_state.rb +0 -50
  439. data/lib/badline/reu/dma.rb +0 -273
  440. data/lib/badline/reu/ram.rb +0 -107
  441. data/lib/badline/reu/saved_state.rb +0 -80
  442. data/lib/badline/reu/trigger.rb +0 -32
  443. data/lib/badline/reu.rb +0 -289
  444. data/lib/badline/sid/catch_up.rb +0 -120
  445. data/lib/badline/sid/decimator.rb +0 -64
  446. data/lib/badline/sid/envelope/state.rb +0 -68
  447. data/lib/badline/sid/envelope.rb +0 -239
  448. data/lib/badline/sid/filter.rb +0 -263
  449. data/lib/badline/sid/voice.rb +0 -77
  450. data/lib/badline/sid/waveform/combined.rb +0 -126
  451. data/lib/badline/sid/waveform/fast_forward.rb +0 -105
  452. data/lib/badline/sid/waveform/noise_writeback.rb +0 -122
  453. data/lib/badline/sid/waveform/state.rb +0 -66
  454. data/lib/badline/sid/waveform.rb +0 -305
  455. data/lib/badline/sid.rb +0 -303
  456. data/lib/badline/time_of_day.rb +0 -228
  457. data/lib/badline/traps.rb +0 -19
  458. data/lib/badline/via/control_lines.rb +0 -122
  459. data/lib/badline/via/fast_forward.rb +0 -53
  460. data/lib/badline/via/interrupt_register.rb +0 -67
  461. data/lib/badline/via/shift_register.rb +0 -180
  462. data/lib/badline/via/state.rb +0 -42
  463. data/lib/badline/via/timer1.rb +0 -131
  464. data/lib/badline/via/timer2.rb +0 -133
  465. data/lib/badline/via.rb +0 -266
  466. data/lib/badline/vic/bank.rb +0 -133
  467. data/lib/badline/vic/border_mask.rb +0 -86
  468. data/lib/badline/vic/collisions.rb +0 -115
  469. data/lib/badline/vic/color_patches.rb +0 -47
  470. data/lib/badline/vic/display_state.rb +0 -185
  471. data/lib/badline/vic/graphics_mode.rb +0 -164
  472. data/lib/badline/vic/graphics_shifter.rb +0 -207
  473. data/lib/badline/vic/register_log.rb +0 -81
  474. data/lib/badline/vic/registers.rb +0 -195
  475. data/lib/badline/vic/sequencer.rb +0 -275
  476. data/lib/badline/vic/sequencer_output.rb +0 -116
  477. data/lib/badline/vic/sequencer_state.rb +0 -89
  478. data/lib/badline/vic/sprite/internal_bus.rb +0 -68
  479. data/lib/badline/vic/sprite/saved_state.rb +0 -64
  480. data/lib/badline/vic/sprite/shifter.rb +0 -146
  481. data/lib/badline/vic/sprite/timing.rb +0 -56
  482. data/lib/badline/vic/sprite.rb +0 -337
  483. data/lib/badline/vic/sprites.rb +0 -282
  484. data/lib/badline/vic/sprites_state.rb +0 -57
  485. data/lib/badline/vic.rb +0 -853
  486. data/lib/badline/vic20/sound/output.rb +0 -83
  487. data/lib/badline/vic20/sound.rb +0 -233
  488. data/lib/badline/z80/arithmetic.rb +0 -117
  489. data/lib/badline/z80/bit_page.rb +0 -55
  490. data/lib/badline/z80/block_io.rb +0 -58
  491. data/lib/badline/z80/block_transfers.rb +0 -59
  492. data/lib/badline/z80/bus_cycles.rb +0 -111
  493. data/lib/badline/z80/core.rb +0 -113
  494. data/lib/badline/z80/exchanges.rb +0 -50
  495. data/lib/badline/z80/extended_page.rb +0 -89
  496. data/lib/badline/z80/flags.rb +0 -37
  497. data/lib/badline/z80/interrupt_acceptance.rb +0 -46
  498. data/lib/badline/z80/jumps.rb +0 -82
  499. data/lib/badline/z80/loads.rb +0 -98
  500. data/lib/badline/z80/main_page.rb +0 -134
  501. data/lib/badline/z80/registers.rb +0 -120
  502. data/lib/badline/z80/saved_state.rb +0 -69
  503. data/lib/badline/z80/shifts.rb +0 -64
  504. data/lib/badline/z80.rb +0 -11
  505. /data/lib/badline/snapshot/{fields.rb → vice/fields.rb} +0 -0
@@ -0,0 +1,783 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "badline/chips/vic/bank"
4
+ require "badline/chips/vic/registers"
5
+ require "badline/chips/vic/display_state"
6
+ require "badline/chips/vic/sequencer"
7
+ require "badline/chips/vic/sprites"
8
+ require "badline/chips/vic/saved_state"
9
+
10
+ module Badline
11
+ module Chips
12
+ class VIC
13
+ include Addressable
14
+ include IntegerHelper
15
+ include SavedState
16
+
17
+ # The 6569 of the breadbin C64, the HMOS 8565 of the C64C, and the
18
+ # C128's VIC-IIe, the 8566 (PAL) and 8564 (NTSC). The VIC-IIe draws as
19
+ # the 8565 and adds the $D02F and $D030 registers.
20
+ MODELS = %i[mos6569 mos8565 mos8566 mos8564].freeze
21
+
22
+ # The sixteen colours #display's palette indices stand for, as RGB.
23
+ PALETTE = [
24
+ 0x000000, 0xffffff, 0x924a40, 0x84c5cc,
25
+ 0x9351b6, 0x72b14b, 0x483aaa, 0xd5df7c,
26
+ 0x675200, 0xc33d00, 0xc18178, 0x606060,
27
+ 0x8a8a8a, 0xb3ec91, 0x867ade, 0xb3b3b3
28
+ ].freeze
29
+
30
+ # The sixteen colours' luminances, out of 32, as Philip Timmermann
31
+ # (Pepto) measured them on the 6569.
32
+ LUMINANCES = [0, 32, 10, 20, 12, 16, 8, 24, 12, 8, 16, 10, 15, 24, 15, 20].freeze
33
+
34
+ # The sixteen colours on a green monochrome monitor, which shows only
35
+ # their luminance, as the PET 64's does.
36
+ GREEN_PALETTE = LUMINANCES.map do |luminance|
37
+ green = luminance * 255 / 32
38
+ ((green / 4) << 16) | (green << 8) | (green / 4)
39
+ end.freeze
40
+
41
+ attr_reader :display, :width, :height, :vic_bank, :column,
42
+ :rasterline, :dirty_lines, :model, :region
43
+
44
+ # The RGB colours #display's palette indices stand for: PALETTE, or
45
+ # GREEN_PALETTE on a monochrome monitor.
46
+ attr_accessor :palette
47
+
48
+ # The line of #display the beam draws, which is the raster line unless
49
+ # the VIC-IIe's TEST bit has moved the raster counter on since the
50
+ # last vertical sync (#test_step!).
51
+ attr_reader :output_line
52
+
53
+ # The parts a VICE snapshot reads and sets.
54
+ attr_reader :registers, :display_state, :sequencer, :sprites, :character_buffer, :color_buffer,
55
+ :fetch_d011
56
+
57
+ # Returns the byte the CPU's halted read would see, for the c-accesses
58
+ # that run before AEC. Without it they read colour RAM.
59
+ attr_writer :open_bus
60
+
61
+ LIGHTPEN_IRQ = 0x08 # $D019 latch bit
62
+ COLOR_REGISTERS = 0x20..0x2e
63
+
64
+ # What the 8565 shows on the pixel before a color register write takes
65
+ # hold, where the 6569 shows the old color: the grey dot.
66
+ GREY_DOT = 0x0f
67
+
68
+ # A g-access reaches the pixel output this many columns after it runs.
69
+ GRAPHICS_DELAY = 2
70
+
71
+ # What a column's g-access passed to the sequencer.
72
+ G_BLANK = 0
73
+ G_IDLE = 1
74
+ G_DISPLAY = 2
75
+
76
+ # The idle g-access in the column a mid-row bad line opens display
77
+ # state reads here on a 6569.
78
+ DMA_DELAY_IDLE_ADDRESS = 0x38ff
79
+
80
+ # The $d011 mode bits a g-access still sees for a cycle after they fall:
81
+ # BMM always, and ECM too when the access it leaves read the character
82
+ # ROM.
83
+ FETCH_HOLD = 0x20
84
+ FETCH_HOLD_ROM = 0x60
85
+
86
+ # The hooks a column can carry, as bits: MCBASE, the left border
87
+ # compare with the end of the sprite, the sprite DMA compare, the
88
+ # expansion flip-flop, the sprite display compare, and the start of the
89
+ # vertical border for the line after the last column.
90
+ HOOK_MCBASE = 1
91
+ HOOK_LEFT = 2
92
+ HOOK_DMA = 4
93
+ HOOK_EXPANSION = 8
94
+ HOOK_DISPLAY = 16
95
+ HOOK_LAST = 32
96
+
97
+ # The vertical sync starts this many lines into the vertical blank,
98
+ # and lasts VSYNC_LINES.
99
+ VSYNC_DELAY = 3
100
+ VSYNC_LINES = 3
101
+
102
+ # On the 6569 the DMA compares fall in columns 53 and 54 and the
103
+ # display compare in column 57. A region whose sprite fetches run later
104
+ # moves them with the fetches (Region::Profile#sprite_cycle).
105
+ DMA_COLUMN = 53
106
+ EXPANSION_COLUMN = 54
107
+
108
+ # BA falls three columns ahead of each sprite's pair of s-accesses, and
109
+ # the five-column windows step two columns apart from sprite 0 at column
110
+ # 54 on the 6569, a column later where the fetches are. From sprite 3 on
111
+ # they reach past the end of the line, so each is split: the tail
112
+ # columns fall on the line whose DMA compare started the fetch, the head
113
+ # columns on the line after it.
114
+ SPRITE_BA_FIRST = 54
115
+ SPRITE_BA_LENGTH = 5
116
+
117
+ def initialize(debug: false, model: :mos6569, region: Region::PAL)
118
+ raise ArgumentError, "unknown VIC-II model #{model}" unless MODELS.include?(model)
119
+
120
+ addressable_at(0xd000, length: 2**10)
121
+ @model = model
122
+ @iie = %i[mos8566 mos8564].include?(model)
123
+ @core = model == :mos6569 ? :mos6569 : :mos8565
124
+ @region = region
125
+ @palette = PALETTE
126
+ @lightpen_extra = @core == :mos8565 ? 1 : 2
127
+ @grey_dots = @core == :mos8565
128
+ @delayed_fetch = @core == :mos8565
129
+ @dma_delay_idle = @core == :mos6569
130
+ @bank_swaps = @core == :mos8565
131
+ @vic_bank = VIC::Bank.new(self)
132
+ @debug = debug
133
+
134
+ @columns_per_line = region.cycles_per_line
135
+ @sprite_cycle = region.sprite_cycle
136
+ @sprite_shift = region.sprite_cycle - Region::PAL.sprite_cycle
137
+ @width = @columns_per_line * 8
138
+ @height = region.lines_per_frame
139
+ @last_column = @columns_per_line - 1
140
+ @last_line = @height - 1
141
+ @vsync_line = region.vblank[0] + VSYNC_DELAY
142
+ layout_columns
143
+ @blank_columns = Array.new(@columns_per_line) { |column| Region.blanked?(column, region.hblank) }.freeze
144
+ @blank_lines = Array.new(@height) { |line| Region.blanked?(line, region.vblank) }.freeze
145
+ @display = Array.new(@width * @height, 0)
146
+ @lines = Array.new(@height) { Array.new(@width, 0) }
147
+ @dirty_lines = Array.new(@height, true)
148
+ @render = true
149
+
150
+ power_on!
151
+ end
152
+
153
+ # The VIC has no reset pin, so only a power cycle brings back the
154
+ # registers, the raster position and the fetch and sprite state it
155
+ # starts with. The display keeps its buffers, cleared to black.
156
+ def power_on!
157
+ @registers = VIC::Registers.new(iie: @iie)
158
+ @register_bytes = @registers.bytes
159
+ @display_state = VIC::DisplayState.new(@registers, @last_column)
160
+ @sequencer = VIC::Sequencer.new(@width, @registers, @vic_bank, model: @core, region: @region)
161
+ @sequencer.render = @render
162
+ @sprites = VIC::Sprites.new(@registers, @vic_bank, @width, model: @core, region: @region)
163
+ @display.fill(0)
164
+ @lines.each { |line| line.fill(0) }
165
+ @dirty_lines.fill(true)
166
+
167
+ @column = 0
168
+ @rasterline = 0
169
+ @output_line = 0
170
+
171
+ @character_buffer = Array.new(40, 0)
172
+ @color_buffer = Array.new(40, 0)
173
+ @sprite_ba = Array.new(@width / 8, false)
174
+ @g_tick = 0
175
+ @g_kind = Array.new(4, G_BLANK)
176
+ @g_kept_char = 0
177
+ @g_kept_color = 0
178
+ @g_data = @sequencer.ring_data
179
+ @g_char = @sequencer.ring_char
180
+ @g_color = @sequencer.ring_color
181
+ @g_display = false
182
+ @fetch_d011 = @register_bytes[0x11]
183
+ @lp_triggered = false
184
+ @lp_low = false
185
+ @raster_match = false
186
+ @test_wrap = false
187
+ end
188
+
189
+ def cycle!
190
+ start_line! if @column.zero?
191
+
192
+ # The g-access runs in the first half of the cycle, ahead of the bad
193
+ # line compare, and the c-access in the second half, after it.
194
+ draw!
195
+ dma_delay_idle_access if @display_state.cycle(@rasterline, @column)
196
+
197
+ fetch_character_data! if dma_active?
198
+
199
+ hooks = @hook_columns[@column]
200
+ column_hooks(hooks) if hooks
201
+
202
+ @column += 1
203
+ if @column == @columns_per_line
204
+ finish_line!
205
+ @column = 0
206
+ step_output_line
207
+ @rasterline = @rasterline == @last_line ? 0 : @rasterline + 1
208
+ check_raster_irq!(@rasterline.zero? ? @last_line : @rasterline)
209
+ end
210
+ watch_collisions
211
+ nil
212
+ end
213
+
214
+ # The sprite and border hooks that fall on named cycles, one column
215
+ # ahead of Bauer's numbering — two for MCBASE, the DMA compares and the
216
+ # expansion flip-flop, which the `spriteenable` and `spritecrunch`
217
+ # references place a column earlier still. Guarded in
218
+ # #cycle! so an ordinary column pays an array read rather than the
219
+ # dispatch.
220
+ def column_hooks(hooks)
221
+ @sprites.advance_mcbase if hooks.anybits?(HOOK_MCBASE)
222
+ if hooks.anybits?(HOOK_LEFT)
223
+ @sequencer.left_compare_vertical_border
224
+ finish_sprite_mcbase
225
+ end
226
+ check_sprite_dma if hooks.anybits?(HOOK_DMA)
227
+ @sprites.toggle_expansion if hooks.anybits?(HOOK_EXPANSION)
228
+ @sprites.check_display(@rasterline) if hooks.anybits?(HOOK_DISPLAY)
229
+ @sequencer.start_vertical_border(@rasterline == @last_line ? 0 : @rasterline + 1) if hooks.anybits?(HOOK_LAST)
230
+ end
231
+
232
+ # The IRQ line is held asserted while any enabled latch bit is set in
233
+ # $D019/$D01A, until the program acknowledges it by writing to $D019.
234
+ def interrupted?
235
+ @registers.irq_line?
236
+ end
237
+
238
+ # The VIC-IIe's $D02F lines K0-K2 and $D030 bits, for the machine to
239
+ # read. The 6569 and 8565 give 7, false and false.
240
+ def extra_keyboard_lines = @registers.extra_keyboard_lines
241
+ def fast? = @registers.fast?
242
+ def test? = @registers.test?
243
+
244
+ # $D030's FAST and TEST bits as bits 0 and 1, which the C128 reads once
245
+ # a cycle.
246
+ def clock_bits = @registers.clock_bits
247
+
248
+ # Whether the cycle just run was one of the five refresh cycles, Bauer's
249
+ # 11-15, whose phi1 half the VIC keeps in the VIC-IIe's FAST mode.
250
+ # @column has already advanced, so it is the Bauer cycle less one.
251
+ def refresh_cycle? = @column.between?(10, 14)
252
+
253
+ # In the VIC-IIe's FAST mode the 8502 drives the bus in both halves of
254
+ # the cycle, and the VIC's accesses in the cycle just run latched what
255
+ # it put there. The g-access, or the idle access, took the byte of
256
+ # phi1. A c-access took the byte of phi2 as one made before AEC does
257
+ # (#fetch_character_data!): $ff for the video matrix, unless the CPU
258
+ # was reading or writing the VIC's own registers, and the byte's low
259
+ # nibble for colour. AEC can't follow BA down while the CPU runs at
260
+ # 2 MHz, so on a bad line it falls three cycles after FAST mode ends.
261
+ def take_cpu_bus(phi1, phi2, register_access)
262
+ slot = @g_tick
263
+ @g_data[slot] = phi1 unless @g_kind[slot] == G_BLANK
264
+ display_state = @display_state
265
+ display_state.keep_bus(@column)
266
+ return unless display_state.fetching?(@column.zero? ? @last_column : @column - 1)
267
+
268
+ vmli = display_state.vmli
269
+ @character_buffer[vmli] = register_access ? phi2 : 0xff
270
+ @color_buffer[vmli] = phi2 & 0x0f
271
+ end
272
+
273
+ # The VIC-IIe's TEST bit clocks the raster counter in every cycle, and
274
+ # the C128 calls this ahead of each cycle the bit is set in. The line's
275
+ # last cycle, whose own step it falls in with, adds nothing more. The
276
+ # counter takes two steps from the frame's last line to line 0, as line
277
+ # 0 reads the last line for its first cycle.
278
+ def test_step!
279
+ return if @column == @last_column
280
+
281
+ if @rasterline == @last_line
282
+ @test_wrap = !@test_wrap
283
+ return if @test_wrap
284
+
285
+ @rasterline = 0
286
+ @display_state.new_frame
287
+ else
288
+ @test_wrap = false
289
+ @rasterline += 1
290
+ end
291
+ @display_state.raster_step(@rasterline)
292
+ check_raster_irq!
293
+ end
294
+
295
+ def peek(addr)
296
+ i = offset_of(addr) % (2**6)
297
+ case i
298
+ when 0x11 then (@registers[0x11] & 0x7f) | ((raster_counter & 0x100) >> 1)
299
+ when 0x12 then raster_counter & 0xff
300
+ when 0x19 then irq_status # Latch + master IRQ bit, unused bits read 1
301
+ when 0x1e, 0x1f then read_collision(i)
302
+ else @registers.read(i)
303
+ end.tap { |value| @sprites.bus_data(@column, value) }
304
+ end
305
+
306
+ # The register file as stored, read without a bus access's side effects.
307
+ def register_file = Array.new(0x40) { |reg| @registers[reg] }
308
+
309
+ def poke(addr, value)
310
+ reg = offset_of(addr) % (2**6)
311
+ @sprites.bus_data(@column, value)
312
+ log_register_change(reg, value)
313
+ @registers.write(reg, value)
314
+ compare_raster_writes(reg)
315
+ end
316
+
317
+ def position
318
+ (@rasterline * @width) + (@column * 8)
319
+ end
320
+
321
+ def dma_active?
322
+ @display_state.fetching?(@column)
323
+ end
324
+
325
+ # Asked once the VIC has advanced, so @column is one ahead of the cycle
326
+ # the CPU is about to run. A bad line holds BA on the condition as it
327
+ # stands rather than the latched match.
328
+ def ba_low?
329
+ return true if @sprite_ba[@column]
330
+
331
+ @display_state.bad_line_condition? &&
332
+ @column > DisplayState::DMA_FIRST && @column <= DisplayState::DMA_LAST + 1
333
+ end
334
+
335
+ # Whether this is the first cycle of sprite 0's BA window on the line
336
+ # whose raster matches its Y, where the sprite's DMA starts. An REU
337
+ # that moved a byte on the cycle before doesn't see BA fall here. Pinned by
338
+ # REU/bonzai/spritetiming.
339
+ def reu_ba_late?
340
+ @column == SPRITE_BA_FIRST + @sprite_shift && sprite_zero_starting?
341
+ end
342
+
343
+ # Whether BA passes straight from a bad line's window to sprite 0's on
344
+ # this cycle, the first of sprite 0's. An REU sees the bad line's DMA
345
+ # end here. Pinned by REU/reutiming2/e4-m2 and e6-m2.
346
+ def reu_ba_handed_on?
347
+ @column == DisplayState::DMA_LAST + 2 && @sprite_ba[@column] && @display_state.bad_line_condition?
348
+ end
349
+
350
+ # Light pen input level (CIA1 PB4). A falling edge triggers the latch.
351
+ def lightpen_level(high)
352
+ @lp_low = !high
353
+ trigger_lightpen if @lp_low
354
+ end
355
+
356
+ # The first trigger per frame latches the beam position into LPX/LPY and
357
+ # raises the LP IRQ. The latch happens one cycle after the edge, with
358
+ # the model's extra half-pixels, two on the 6569 and one on the 8565; a
359
+ # trigger on the last line is consumed without latching unless it lands
360
+ # on the line's first cycle.
361
+ def trigger_lightpen
362
+ return if @lp_triggered
363
+
364
+ @lp_triggered = true
365
+ column = @column + 1
366
+ line = @rasterline
367
+ if column == @columns_per_line
368
+ column = 0
369
+ line = line == @last_line ? 0 : line + 1
370
+ end
371
+ return if line == @last_line && column.positive?
372
+
373
+ vic_x = Sprite::Timing.xpos(column * 8, @width, @region.x_hold)
374
+ latch_lightpen((vic_x >> 1) + @lightpen_extra, line)
375
+ end
376
+
377
+ # The byte the VIC fetched in the phi1 half of the cycle the CPU is
378
+ # running, worked out from the VIC's state. The CPU cycle after column
379
+ # c is Bauer cycle c + 2, and each Bauer cycle has a fixed access (VICE
380
+ # `cycle_phi1_fetch`).
381
+ def phi1_data
382
+ vic_bank.peek(phi1_address)
383
+ end
384
+
385
+ def hblank? = @blank_columns[@column]
386
+ def vblank? = @blank_lines[@rasterline]
387
+ def blanking? = @blank_lines[@rasterline] || @blank_columns[@column]
388
+
389
+ def render? = @render
390
+
391
+ # Whether finished lines are painted into #display. A headless run that
392
+ # only reads the machine's state can turn it off: the sprite
393
+ # collisions, lightpen and every register still behave the same, but
394
+ # #display keeps the last lines painted. Turned back on, it is whole
395
+ # again from the start of the next frame.
396
+ def render=(on)
397
+ @render = on
398
+ @sequencer.render = on
399
+ end
400
+
401
+ # Puts the beam at the start of `line`.
402
+ def restore_line(line)
403
+ @rasterline = @output_line = line
404
+ @column = 0
405
+ end
406
+
407
+ # The latches a VICE snapshot holds: the raster match, the light pen's
408
+ # line held low and its trigger, as bits 0, 1 and 2.
409
+ def latch_bits = (@raster_match ? 1 : 0) | (@lp_low ? 2 : 0) | (@lp_triggered ? 4 : 0)
410
+
411
+ # Sets the latches from latch_bits, and the $D011 the next g-access
412
+ # holds.
413
+ def restore_latches(bits, fetch_d011)
414
+ @raster_match = bits.anybits?(1)
415
+ @lp_low = bits.anybits?(2)
416
+ @lp_triggered = bits.anybits?(4)
417
+ @fetch_d011 = fetch_d011
418
+ end
419
+
420
+ # Marks the columns BA is low for the sprites whose DMA runs.
421
+ def rebuild_sprite_ba
422
+ @sprite_ba.fill(false)
423
+ 8.times do |n|
424
+ next unless @sprites[n].displaying?
425
+
426
+ @sprite_ba_tail[n].each { |c| @sprite_ba[c] = true }
427
+ @sprite_ba_head[n].each { |c| @sprite_ba[c] = true }
428
+ end
429
+ end
430
+
431
+ # Reset the dirty flags once the frontend has consumed them.
432
+ def clear_dirty_lines!
433
+ @dirty_lines.fill(false)
434
+ end
435
+
436
+ private
437
+
438
+ # The raster counter as $D011/$D012 read it. Every line advances on the
439
+ # CPU cycle paired with column 62 except line 0, which the counter
440
+ # reaches a cycle later, together with the line 0 raster compare.
441
+ def raster_counter
442
+ @column.zero? && @rasterline.zero? ? @last_line : @rasterline
443
+ end
444
+
445
+ # Mid-line writes to the color and sprite registers are logged against
446
+ # the cycle after the write (the CPU runs after the VIC within a machine
447
+ # cycle, so @column already points there); each register adds its own
448
+ # pixel delay on top. On the 8565 a color register write that leaves
449
+ # the value as it was still shows its grey dot.
450
+ def log_register_change(reg, value)
451
+ old = @registers[reg]
452
+ return if old == value && !grey_dot?(reg)
453
+
454
+ if (0x20..0x24).cover?(reg)
455
+ @sequencer.colors_changed! unless reg == 0x20
456
+ @sequencer.color_patches.log(reg, old, value, @column * 8) if @render && !blanking?
457
+ else
458
+ log_sprite_change(reg, old, value)
459
+ end
460
+ end
461
+
462
+ def grey_dot?(reg) = @grey_dots && COLOR_REGISTERS.cover?(reg)
463
+
464
+ # A sprite color shows its grey dot as a write of light grey on the
465
+ # pixel before the new color.
466
+ def log_sprite_change(reg, old, value)
467
+ beam_x = @column * 8
468
+ @sprites.log_change(reg, old, GREY_DOT, beam_x - 1) if grey_dot?(reg)
469
+ @sprites.log_change(reg, old, value, beam_x)
470
+ end
471
+
472
+ # On the 6569 Bauer cycles 1-10 and 58-63 are sprite p- and s-accesses,
473
+ # 11-15 refresh, 16-55 g-accesses and 56-57 idle. Where the sprite
474
+ # fetches start later, the idle cycles before them grow, and on the
475
+ # 6567R8 cycle 10 idles too. @column has already advanced, so it is the
476
+ # Bauer cycle less one.
477
+ def phi1_address
478
+ cycle = @column + 1
479
+ slot = (cycle - @sprite_cycle) % @columns_per_line
480
+ if slot < 16
481
+ sprite_phi1_address(slot)
482
+ elsif cycle.between?(11, 15)
483
+ 0x3f00 | ((0xff - (5 * @rasterline) - (cycle - 11)) & 0xff)
484
+ elsif cycle.between?(16, 55)
485
+ graphics_phi1_address
486
+ else
487
+ 0x3fff
488
+ end
489
+ end
490
+
491
+ # Each sprite takes two cycles from sprite 0's, the p-access and then
492
+ # the middle s-access.
493
+ def sprite_phi1_address(slot)
494
+ n = slot >> 1
495
+ return @registers.screen_base + 0x3f8 + n if slot.even?
496
+
497
+ @sprites[n].phi1_address || 0x3fff
498
+ end
499
+
500
+ # The g-access of the column just run. In display state it used the
501
+ # VC and VMLI it then stepped past.
502
+ def graphics_phi1_address
503
+ return @registers.ecm.nonzero? ? 0x39ff : 0x3fff unless @g_display
504
+
505
+ graphics_address(@registers[0x11], @display_state.vmli - 1, (@display_state.vc - 1) & 0x3ff)
506
+ end
507
+
508
+ # A $d011/$d012 write is compared in the next column. A write in the
509
+ # line's second last column is left to its last, which compares the
510
+ # next line.
511
+ def compare_raster_writes(reg)
512
+ return unless (0x11..0x12).cover?(reg)
513
+ return if @column == @last_column
514
+
515
+ check_raster_irq!
516
+ @sequencer.compare_vertical_border(@rasterline) if reg == 0x11
517
+ end
518
+
519
+ def check_sprite_dma
520
+ rebuild_sprite_ba if @sprites.check_dma(@rasterline, @column)
521
+ end
522
+
523
+ # A sprite whose DMA ends here fetches nothing at the end of this line,
524
+ # so its BA tail goes too.
525
+ def finish_sprite_mcbase
526
+ @sprites.finish_mcbase
527
+ rebuild_sprite_ba if @sprites.stopped_dma?
528
+ end
529
+
530
+ # The trigger re-arms at the start of each frame; if the pen line is
531
+ # still low, the latch retriggers immediately with a fixed LPX of $d1.
532
+ def start_lightpen_frame
533
+ @lp_triggered = false
534
+ return unless @lp_low
535
+
536
+ @lp_triggered = true
537
+ latch_lightpen(0xd1, 0)
538
+ end
539
+
540
+ def latch_lightpen(lpx, line)
541
+ @registers.write(0x13, lpx & 0xff)
542
+ @registers.write(0x14, line & 0xff)
543
+ @registers.latch_irq!(LIGHTPEN_IRQ)
544
+ end
545
+
546
+ def sprite_zero_starting?
547
+ @registers[0x15].anybits?(0x01) && @registers[0x01] == (@rasterline & 0xff)
548
+ end
549
+
550
+ # Splits the sprite BA windows at the end of the line and marks the hook
551
+ # columns, for a line of the region's length: 14, 15, 53, 54 and 57, and
552
+ # the line's last column, which starts the vertical border for the line
553
+ # after it. The marks let an ordinary column cost one array read instead
554
+ # of the dispatch.
555
+ def layout_columns
556
+ last = @last_column
557
+ windows = Array.new(8) do |n|
558
+ first = SPRITE_BA_FIRST + @sprite_shift + (2 * n)
559
+ first...(first + SPRITE_BA_LENGTH)
560
+ end
561
+ @sprite_ba_tail = windows.map { |w| w.select { |c| c <= last } }.freeze
562
+ @sprite_ba_head = windows.map { |w| w.filter_map { |c| c - last - 1 if c > last } }.freeze
563
+ @hook_columns = layout_hooks.map { |hooks| hooks unless hooks.zero? }.freeze
564
+ end
565
+
566
+ def layout_hooks
567
+ hooks = Array.new(@columns_per_line, 0)
568
+ hooks[14] |= HOOK_MCBASE
569
+ hooks[15] |= HOOK_LEFT
570
+ hooks[DMA_COLUMN + @sprite_shift] |= HOOK_DMA
571
+ hooks[DMA_COLUMN + @sprite_shift + 1] |= HOOK_DMA
572
+ hooks[EXPANSION_COLUMN] |= HOOK_EXPANSION
573
+ hooks[@region.sprite_display_cycle - 1] |= HOOK_DISPLAY
574
+ hooks[@last_column] |= HOOK_LAST
575
+ hooks
576
+ end
577
+
578
+ # Runs this column's g-access and draws the one from GRAPHICS_DELAY
579
+ # columns earlier. The access reads its byte now, through the mode,
580
+ # $d018 and bank of this cycle. A column with no g-access, or one made
581
+ # while the vertical border flip-flop is set, latches nothing: it passes
582
+ # on zero data with the screen byte and colour the last g-access
583
+ # latched, which an idle g-access clears.
584
+ def draw!
585
+ @vic_bank.sample_lines if @bank_swaps
586
+ slot = @g_tick = (@g_tick + 1) & 3
587
+ display_state = @display_state
588
+ access = display_state.graphics_column?(@column) && !@sequencer.vertical_closed?
589
+ if access
590
+ latch_graphics(slot, display_state)
591
+ else
592
+ @g_kind[slot] = G_BLANK
593
+ @g_data[slot] = 0
594
+ @g_char[slot] = @g_kept_char
595
+ @g_color[slot] = @g_kept_color
596
+ end
597
+ @g_display = display_state.display?
598
+ display_state.graphics_access(@column) if @g_display
599
+ emit_graphics((slot - GRAPHICS_DELAY) & 3, @column - 16) unless blanking?
600
+ @sequencer.latch_xscroll(@register_bytes[0x16] & 0b111) if access
601
+ @fetch_d011 = @register_bytes[0x11]
602
+ end
603
+
604
+ def latch_graphics(slot, display_state)
605
+ unless display_state.display?
606
+ @g_kind[slot] = G_IDLE
607
+ @g_data[slot] = vic_bank.peek(@registers.ecm.zero? ? 0x3fff : 0x39ff)
608
+ @g_char[slot] = @g_color[slot] = @g_kept_char = @g_kept_color = 0
609
+ return
610
+ end
611
+
612
+ vmli = display_state.vmli
613
+ @g_kind[slot] = G_DISPLAY
614
+ @g_data[slot] = fetch_graphics(vmli, display_state.vc)
615
+ @g_char[slot] = @g_kept_char = @character_buffer[vmli] || 0
616
+ @g_color[slot] = @g_kept_color = @color_buffer[vmli] || 1
617
+ end
618
+
619
+ # On the 6569 a bad line that opens display state mid-row (DMA delay)
620
+ # moves the idle g-access of the column that triggers it from $3fff (or
621
+ # $39ff) to DMA_DELAY_IDLE_ADDRESS, unless YSCROLL is 0 (vsp-tester,
622
+ # colorfetchbug/main).
623
+ def dma_delay_idle_access
624
+ slot = @g_tick
625
+ return unless @dma_delay_idle && @g_kind[slot] == G_IDLE
626
+ return if @registers.yscroll.zero?
627
+
628
+ @g_data[slot] = vic_bank.peek(DMA_DELAY_IDLE_ADDRESS)
629
+ end
630
+
631
+ # The 6569's g-access sees a mode bit that falls a cycle late: it
632
+ # addresses with $d011 as this column has it, OR-ed with the held bits
633
+ # the column before had. When BMM changes and the access moves from RAM
634
+ # onto the character ROM, the low address byte still comes from the old
635
+ # mode. The 8565's addresses with $d011 as the column before had it
636
+ # (VICE x64sc `vicii_fetch_graphics`).
637
+ def fetch_graphics(vmli, counter)
638
+ d011 = @register_bytes[0x11]
639
+ last = @fetch_d011
640
+ return vic_bank.peek(graphics_address(d011, vmli, counter)) if d011 == last
641
+ return vic_bank.peek(graphics_address(last, vmli, counter)) if @delayed_fetch
642
+
643
+ from = graphics_address(last, vmli, counter)
644
+ from_rom = vic_bank.character_rom?(from)
645
+ address = graphics_address(d011 | (last & (from_rom ? FETCH_HOLD_ROM : FETCH_HOLD)), vmli, counter)
646
+ if (d011 ^ last).anybits?(0x20) && !from_rom
647
+ to = graphics_address(d011, vmli, counter)
648
+ address = (from & 0xff) | (to & 0x3f00) if vic_bank.character_rom?(to)
649
+ end
650
+ vic_bank.peek(address)
651
+ end
652
+
653
+ def graphics_address(d011, vmli, counter)
654
+ rc = @display_state.rc
655
+ case d011 & 0x60
656
+ when 0x00 then @registers.char_base | ((@character_buffer[vmli] || 0) << 3) | rc
657
+ when 0x20 then @registers.bitmap_base | (counter << 3) | rc
658
+ when 0x40 then (@registers.char_base | ((@character_buffer[vmli] || 0) << 3) | rc) & 0x39ff
659
+ else (@registers.bitmap_base | (counter << 3) | rc) & 0x39ff
660
+ end
661
+ end
662
+
663
+ def emit_graphics(slot, col)
664
+ @sequencer.emit(slot, col, @g_kind[slot] == G_DISPLAY)
665
+ end
666
+
667
+ # Fold the rest of the line's sprite collisions in — they latch whether
668
+ # or not there is a line to draw — then composite the active sprites
669
+ # over the finished background and copy the line into the frame display.
670
+ def finish_line!
671
+ return finish_blank_line if vblank? || !@render
672
+
673
+ composite = @sprites.active?
674
+ @sequencer.apply_color_patches
675
+ @sequencer.snapshot_line if composite
676
+ @sprites.finish_line(composite ? @sequencer.colors : nil, @sequencer.fg)
677
+ @sequencer.apply_border if composite
678
+
679
+ colors = @sequencer.colors
680
+ output_line = @output_line
681
+ line = @lines[output_line]
682
+ return if line == colors
683
+
684
+ line[0, @width] = colors
685
+ @display[output_line * @width, @width] = colors
686
+ @dirty_lines[output_line] = true
687
+ end
688
+
689
+ # A blanked line paints nothing, and is never shown unless the TEST
690
+ # bit has moved the beam off the raster line, where it shows black.
691
+ def finish_blank_line
692
+ @sprites.finish_line(nil, @sequencer.fg)
693
+ return if @output_line == @rasterline || !@render
694
+
695
+ line = @output_line
696
+ @lines[line].fill(0)
697
+ @display.fill(0, line * @width, @width)
698
+ @dirty_lines[line] = true
699
+ end
700
+
701
+ # The display line the beam draws next follows the one before it, and
702
+ # a line that ends in the vertical sync puts it back on the raster
703
+ # line. Only the VIC-IIe's TEST bit moves the raster counter on between
704
+ # the two, so the lines it skips shift the picture up until the next
705
+ # sync.
706
+ def step_output_line
707
+ @output_line = if @rasterline.between?(@vsync_line, @vsync_line + VSYNC_LINES - 1)
708
+ @rasterline + 1
709
+ else
710
+ @output_line == @last_line ? 0 : @output_line + 1
711
+ end
712
+ end
713
+
714
+ def video_matrix(index)
715
+ vic_bank.peek_phi2(@registers.screen_base + index)
716
+ end
717
+
718
+ def start_line!
719
+ if @rasterline.zero?
720
+ @display_state.new_frame
721
+ start_lightpen_frame
722
+ # The line 0 raster compare happens one cycle later than on all
723
+ # other lines (Bauer 3.12).
724
+ check_raster_irq!
725
+ end
726
+ @sequencer.new_line(@rasterline)
727
+ @sprites.start_line
728
+ rebuild_sprite_ba
729
+ @display_state.new_line(@rasterline)
730
+ end
731
+
732
+ # The raster compare latches the flag only as the line and the target
733
+ # come to match, so a target moved along with the raster line holds the
734
+ # match without latching again (VICE x64sc). It runs at each line step
735
+ # and after each $d011/$d012 write. The line asserts via #interrupted?
736
+ # when the mask bit in $D01A is set.
737
+ def check_raster_irq!(line = @rasterline)
738
+ match = line == @registers.raster_target
739
+ @registers.latch_raster_irq! if match && !@raster_match
740
+ @raster_match = match
741
+ end
742
+
743
+ # A collision register carries the pixels drawn up to the cycle before
744
+ # the read — the VIC runs ahead of the CPU within a machine cycle, so
745
+ # the column the read lands on has not latched yet — and the reset the
746
+ # read asserts outlives it, swallowing the pixels drawn under it.
747
+ def read_collision(reg)
748
+ beam_x = @column * 8
749
+ @sprites.collide_upto(beam_x, @sequencer.fg)
750
+ @registers.read(reg).tap { @sprites.clear_collision(reg, beam_x) }
751
+ end
752
+
753
+ # A collision raises its $D019 bit as the beam crosses the pixel rather
754
+ # than when the line is folded at its end, so while an enabled
755
+ # collision IRQ is still unlatched the fold keeps up with the beam.
756
+ def watch_collisions
757
+ return if (@registers[0x1a] & ~@registers[0x19]).nobits?(0x06)
758
+
759
+ @sprites.collide_upto(@column * 8, @sequencer.fg)
760
+ end
761
+
762
+ def irq_status
763
+ @sprites.collide_upto(@column * 8, @sequencer.fg)
764
+ (@registers[0x19] & 0x0f) | 0x70 | (interrupted? ? 0x80 : 0)
765
+ end
766
+
767
+ # A c-access that falls between BA and AEC reads a bus the CPU still
768
+ # drives: the video matrix byte comes back as $ff, and the colour
769
+ # nibble is the low nibble of the byte at the halted CPU's PC.
770
+ def fetch_character_data!
771
+ vmli = @display_state.vmli
772
+ vc = @display_state.vc
773
+ if @display_state.bus_taken?(@column)
774
+ @character_buffer[vmli] = video_matrix(vc)
775
+ @color_buffer[vmli] = vic_bank.peek_color(vc)
776
+ else
777
+ @character_buffer[vmli] = 0xff
778
+ @color_buffer[vmli] = @open_bus ? @open_bus.call & 0x0f : vic_bank.peek_color(vc)
779
+ end
780
+ end
781
+ end
782
+ end
783
+ end