badline 0.12.0 → 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 (516) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +25 -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 +4 -4
  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} +6 -6
  17. data/lib/badline/c128/bus.rb +30 -51
  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 +18 -72
  27. data/lib/badline/{computer → c64}/attachments.rb +16 -47
  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/c64/common.rb +34 -0
  73. data/lib/badline/c64/kernal_traps.rb +89 -0
  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 +20 -31
  82. data/lib/badline/{computer.rb → c64.rb} +22 -80
  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 +27 -29
  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 +10 -61
  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/{file.rb → file_routine.rb} +1 -1
  262. data/lib/badline/kernal_trap/load.rb +6 -6
  263. data/lib/badline/kernal_trap/save.rb +5 -5
  264. data/lib/badline/kernal_trap/serial.rb +7 -7
  265. data/lib/badline/kernal_trap.rb +2 -3
  266. data/lib/badline/machine/clocking.rb +50 -0
  267. data/lib/badline/machine.rb +63 -7
  268. data/lib/badline/media/boot_disk.rb +2 -2
  269. data/lib/badline/media/disk_list.rb +3 -3
  270. data/lib/badline/media/disk_set.rb +1 -1
  271. data/lib/badline/media/extensions.rb +33 -0
  272. data/lib/badline/media/program.rb +28 -0
  273. data/lib/badline/media/true_drive.rb +4 -3
  274. data/lib/badline/media/tune.rb +35 -0
  275. data/lib/badline/media/vic20_cartridge.rb +1 -7
  276. data/lib/badline/media/vic20_media.rb +38 -80
  277. data/lib/badline/media.rb +84 -81
  278. data/lib/badline/model.rb +1 -1
  279. data/lib/badline/options/event.rb +3 -1
  280. data/lib/badline/roms/README +6 -6
  281. data/lib/badline/sdl.rb +1 -0
  282. data/lib/badline/{storage/sid_file.rb → sid_tune/file.rb} +3 -3
  283. data/lib/badline/{storage → sid_tune}/hvsc.rb +7 -7
  284. data/lib/badline/{storage → sid_tune}/song_lengths.rb +2 -2
  285. data/lib/badline/{storage → sid_tune}/stil/credit.rb +1 -1
  286. data/lib/badline/{storage → sid_tune}/stil.rb +3 -3
  287. data/lib/badline/sid_tune.rb +6 -0
  288. data/lib/badline/snapshot/c128_setup.rb +43 -0
  289. data/lib/badline/snapshot/c64_setup.rb +79 -0
  290. data/lib/badline/snapshot/image.rb +5 -25
  291. data/lib/badline/snapshot/machine_state.rb +10 -2
  292. data/lib/badline/snapshot/setup.rb +22 -66
  293. data/lib/badline/snapshot/state.rb +8 -17
  294. data/lib/badline/snapshot/storage_setup.rb +68 -0
  295. data/lib/badline/snapshot/vic20_setup.rb +37 -0
  296. data/lib/badline/snapshot/vice/c64_mem.rb +1 -1
  297. data/lib/badline/snapshot/vice/restore.rb +1 -1
  298. data/lib/badline/snapshot/vice/reu1764.rb +1 -1
  299. data/lib/badline/snapshot/vice/reu_fields.rb +40 -0
  300. data/lib/badline/snapshot/vice.rb +7 -5
  301. data/lib/badline/snapshot.rb +13 -12
  302. data/lib/badline/storage/d64_image.rb +15 -9
  303. data/lib/badline/storage/d71_image.rb +0 -2
  304. data/lib/badline/storage/d81_image.rb +0 -2
  305. data/lib/badline/storage/disk_image/writing.rb +2 -2
  306. data/lib/badline/storage/disk_image.rb +3 -6
  307. data/lib/badline/storage/g64_image.rb +2 -2
  308. data/lib/badline/storage/host_directory.rb +0 -5
  309. data/lib/badline/storage/t64.rb +2 -8
  310. data/lib/badline/storage/unavailable.rb +1 -1
  311. data/lib/badline/storage.rb +0 -37
  312. data/lib/badline/support/c128_checkpoint.rb +39 -0
  313. data/lib/badline/support/checkpoint.rb +108 -0
  314. data/lib/badline/support/vic20_checkpoint.rb +38 -0
  315. data/lib/badline/support.rb +5 -0
  316. data/lib/badline/version.rb +1 -1
  317. data/lib/badline/vic20/attachments.rb +11 -81
  318. data/lib/badline/vic20/bus/saved_state.rb +35 -0
  319. data/lib/badline/vic20/bus.rb +3 -25
  320. data/lib/badline/vic20/cpu.rb +1 -1
  321. data/lib/badline/vic20/port_wiring.rb +4 -4
  322. data/lib/badline/vic20/saved_state.rb +16 -60
  323. data/lib/badline/vic20/user_via_ports.rb +2 -2
  324. data/lib/badline/vic20.rb +20 -51
  325. metadata +250 -221
  326. data/lib/badline/address_bus/extra_sids.rb +0 -30
  327. data/lib/badline/address_bus/fittings.rb +0 -38
  328. data/lib/badline/address_bus/pla.rb +0 -147
  329. data/lib/badline/address_bus/roms.rb +0 -39
  330. data/lib/badline/address_bus/saved_state.rb +0 -72
  331. data/lib/badline/address_bus/sid_slots.rb +0 -42
  332. data/lib/badline/address_bus.rb +0 -235
  333. data/lib/badline/cartridge/action_replay.rb +0 -126
  334. data/lib/badline/cartridge/atomic_power.rb +0 -24
  335. data/lib/badline/cartridge/bank.rb +0 -81
  336. data/lib/badline/cartridge/comal80.rb +0 -50
  337. data/lib/badline/cartridge/dinamic.rb +0 -34
  338. data/lib/badline/cartridge/easy_flash.rb +0 -145
  339. data/lib/badline/cartridge/epyx_fastload.rb +0 -79
  340. data/lib/badline/cartridge/final_cartridge3.rb +0 -89
  341. data/lib/badline/cartridge/flash.rb +0 -306
  342. data/lib/badline/cartridge/freezer.rb +0 -33
  343. data/lib/badline/cartridge/fun_play.rb +0 -38
  344. data/lib/badline/cartridge/g_mod2.rb +0 -74
  345. data/lib/badline/cartridge/game_system.rb +0 -40
  346. data/lib/badline/cartridge/geo_ram.rb +0 -79
  347. data/lib/badline/cartridge/kcs_power.rb +0 -75
  348. data/lib/badline/cartridge/mach5.rb +0 -32
  349. data/lib/badline/cartridge/magic_desk.rb +0 -30
  350. data/lib/badline/cartridge/ocean.rb +0 -35
  351. data/lib/badline/cartridge/pagefox.rb +0 -68
  352. data/lib/badline/cartridge/retro_replay.rb +0 -320
  353. data/lib/badline/cartridge/rex_utility.rb +0 -29
  354. data/lib/badline/cartridge/rgcd.rb +0 -58
  355. data/lib/badline/cartridge/simons_basic.rb +0 -35
  356. data/lib/badline/cartridge/standard.rb +0 -15
  357. data/lib/badline/cartridge/super_games.rb +0 -48
  358. data/lib/badline/cartridge/westermann.rb +0 -30
  359. data/lib/badline/cartridge/zaxxon.rb +0 -66
  360. data/lib/badline/cartridge.rb +0 -303
  361. data/lib/badline/checkpoint.rb +0 -98
  362. data/lib/badline/cia/fast_forward.rb +0 -60
  363. data/lib/badline/cia/interrupt_register.rb +0 -143
  364. data/lib/badline/cia/serial.rb +0 -275
  365. data/lib/badline/cia/state.rb +0 -47
  366. data/lib/badline/cia/timer.rb +0 -206
  367. data/lib/badline/cia.rb +0 -279
  368. data/lib/badline/color_memory.rb +0 -25
  369. data/lib/badline/computer/kernal_traps.rb +0 -29
  370. data/lib/badline/control_ports.rb +0 -76
  371. data/lib/badline/cpu/addressing.rb +0 -131
  372. data/lib/badline/cpu/boundary.rb +0 -54
  373. data/lib/badline/cpu/core.rb +0 -165
  374. data/lib/badline/cpu/microcode.rb +0 -177
  375. data/lib/badline/cpu/operations.rb +0 -88
  376. data/lib/badline/cpu/saved_state.rb +0 -115
  377. data/lib/badline/cpu/stack_operations.rb +0 -82
  378. data/lib/badline/cpu.rb +0 -10
  379. data/lib/badline/cycleable.rb +0 -38
  380. data/lib/badline/datasette.rb +0 -161
  381. data/lib/badline/drive/core.rb +0 -147
  382. data/lib/badline/drive/fast_serial.rb +0 -73
  383. data/lib/badline/drive/idle.rb +0 -125
  384. data/lib/badline/drive/idle_cpu.rb +0 -28
  385. data/lib/badline/drive/orbit.rb +0 -79
  386. data/lib/badline/drive/serial_lines.rb +0 -73
  387. data/lib/badline/drive/sleep.rb +0 -112
  388. data/lib/badline/drive/vias.rb +0 -84
  389. data/lib/badline/drive/watch.rb +0 -150
  390. data/lib/badline/drive.rb +0 -11
  391. data/lib/badline/drive1541/bus.rb +0 -86
  392. data/lib/badline/drive1541/byte_ready.rb +0 -48
  393. data/lib/badline/drive1541/cpu.rb +0 -11
  394. data/lib/badline/drive1541/disk.rb +0 -318
  395. data/lib/badline/drive1541/disk_state.rb +0 -94
  396. data/lib/badline/drive1541/disk_via.rb +0 -36
  397. data/lib/badline/drive1541/gcr.rb +0 -57
  398. data/lib/badline/drive1541/mechanism.rb +0 -313
  399. data/lib/badline/drive1541/mechanism_state.rb +0 -68
  400. data/lib/badline/drive1541/rotation.rb +0 -121
  401. data/lib/badline/drive1541/sector_reader.rb +0 -76
  402. data/lib/badline/drive1541/serial_port.rb +0 -18
  403. data/lib/badline/drive1541/track.rb +0 -98
  404. data/lib/badline/drive1541.rb +0 -111
  405. data/lib/badline/drive1571/bus.rb +0 -111
  406. data/lib/badline/drive1571/cpu.rb +0 -11
  407. data/lib/badline/drive1571/serial_port.rb +0 -37
  408. data/lib/badline/drive1571/serial_via.rb +0 -25
  409. data/lib/badline/drive1571/wd1770.rb +0 -120
  410. data/lib/badline/drive1571.rb +0 -175
  411. data/lib/badline/drive1581/bus.rb +0 -137
  412. data/lib/badline/drive1581/cpu.rb +0 -11
  413. data/lib/badline/drive1581/disk.rb +0 -135
  414. data/lib/badline/drive1581/disk_state.rb +0 -87
  415. data/lib/badline/drive1581/mechanism.rb +0 -117
  416. data/lib/badline/drive1581/serial_port.rb +0 -52
  417. data/lib/badline/drive1581/slot.rb +0 -78
  418. data/lib/badline/drive1581/track.rb +0 -100
  419. data/lib/badline/drive1581/track_bytes.rb +0 -75
  420. data/lib/badline/drive1581/wd1772/registers.rb +0 -89
  421. data/lib/badline/drive1581/wd1772/sector_writes.rb +0 -73
  422. data/lib/badline/drive1581/wd1772/sectors.rb +0 -150
  423. data/lib/badline/drive1581/wd1772/state.rb +0 -66
  424. data/lib/badline/drive1581/wd1772/stepping.rb +0 -109
  425. data/lib/badline/drive1581/wd1772/tracks.rb +0 -125
  426. data/lib/badline/drive1581/wd1772/turn.rb +0 -73
  427. data/lib/badline/drive1581/wd1772.rb +0 -216
  428. data/lib/badline/drive1581.rb +0 -233
  429. data/lib/badline/iec_bus.rb +0 -159
  430. data/lib/badline/instruction.rb +0 -334
  431. data/lib/badline/instruction_set/arithmetic.rb +0 -114
  432. data/lib/badline/instruction_set/bitwise.rb +0 -126
  433. data/lib/badline/instruction_set/branch.rb +0 -79
  434. data/lib/badline/instruction_set/flag.rb +0 -63
  435. data/lib/badline/instruction_set/illegal.rb +0 -270
  436. data/lib/badline/instruction_set/inc_dec.rb +0 -69
  437. data/lib/badline/instruction_set/stack.rb +0 -52
  438. data/lib/badline/instruction_set/transfer.rb +0 -137
  439. data/lib/badline/instruction_set.rb +0 -55
  440. data/lib/badline/interrupts.rb +0 -104
  441. data/lib/badline/joystick.rb +0 -25
  442. data/lib/badline/kernal_trap/channel.rb +0 -181
  443. data/lib/badline/keyboard.rb +0 -112
  444. data/lib/badline/ram_expansion/banking.rb +0 -30
  445. data/lib/badline/ram_expansion/plus256k.rb +0 -54
  446. data/lib/badline/ram_expansion/plus60k.rb +0 -43
  447. data/lib/badline/ram_expansion/unexpanded.rb +0 -28
  448. data/lib/badline/ram_expansion.rb +0 -21
  449. data/lib/badline/reu/dma/saved_state.rb +0 -50
  450. data/lib/badline/reu/dma.rb +0 -273
  451. data/lib/badline/reu/ram.rb +0 -107
  452. data/lib/badline/reu/saved_state.rb +0 -80
  453. data/lib/badline/reu/trigger.rb +0 -32
  454. data/lib/badline/reu.rb +0 -289
  455. data/lib/badline/sid/catch_up.rb +0 -120
  456. data/lib/badline/sid/decimator.rb +0 -64
  457. data/lib/badline/sid/envelope/state.rb +0 -68
  458. data/lib/badline/sid/envelope.rb +0 -239
  459. data/lib/badline/sid/filter.rb +0 -263
  460. data/lib/badline/sid/voice.rb +0 -77
  461. data/lib/badline/sid/waveform/combined.rb +0 -126
  462. data/lib/badline/sid/waveform/fast_forward.rb +0 -105
  463. data/lib/badline/sid/waveform/noise_writeback.rb +0 -122
  464. data/lib/badline/sid/waveform/state.rb +0 -66
  465. data/lib/badline/sid/waveform.rb +0 -305
  466. data/lib/badline/sid.rb +0 -303
  467. data/lib/badline/time_of_day.rb +0 -228
  468. data/lib/badline/traps.rb +0 -19
  469. data/lib/badline/via/control_lines.rb +0 -122
  470. data/lib/badline/via/fast_forward.rb +0 -53
  471. data/lib/badline/via/interrupt_register.rb +0 -67
  472. data/lib/badline/via/shift_register.rb +0 -180
  473. data/lib/badline/via/state.rb +0 -42
  474. data/lib/badline/via/timer1.rb +0 -131
  475. data/lib/badline/via/timer2.rb +0 -133
  476. data/lib/badline/via.rb +0 -266
  477. data/lib/badline/vic/bank.rb +0 -133
  478. data/lib/badline/vic/border_mask.rb +0 -86
  479. data/lib/badline/vic/collisions.rb +0 -115
  480. data/lib/badline/vic/color_patches.rb +0 -47
  481. data/lib/badline/vic/display_state.rb +0 -185
  482. data/lib/badline/vic/graphics_mode.rb +0 -164
  483. data/lib/badline/vic/graphics_shifter.rb +0 -207
  484. data/lib/badline/vic/register_log.rb +0 -81
  485. data/lib/badline/vic/registers.rb +0 -195
  486. data/lib/badline/vic/sequencer.rb +0 -275
  487. data/lib/badline/vic/sequencer_output.rb +0 -116
  488. data/lib/badline/vic/sequencer_state.rb +0 -89
  489. data/lib/badline/vic/sprite/internal_bus.rb +0 -68
  490. data/lib/badline/vic/sprite/saved_state.rb +0 -64
  491. data/lib/badline/vic/sprite/shifter.rb +0 -146
  492. data/lib/badline/vic/sprite/timing.rb +0 -56
  493. data/lib/badline/vic/sprite.rb +0 -337
  494. data/lib/badline/vic/sprites.rb +0 -282
  495. data/lib/badline/vic/sprites_state.rb +0 -57
  496. data/lib/badline/vic.rb +0 -852
  497. data/lib/badline/vic20/sound/output.rb +0 -83
  498. data/lib/badline/vic20/sound.rb +0 -233
  499. data/lib/badline/z80/arithmetic.rb +0 -117
  500. data/lib/badline/z80/bit_page.rb +0 -55
  501. data/lib/badline/z80/block_io.rb +0 -58
  502. data/lib/badline/z80/block_transfers.rb +0 -59
  503. data/lib/badline/z80/bus_cycles.rb +0 -111
  504. data/lib/badline/z80/core.rb +0 -113
  505. data/lib/badline/z80/exchanges.rb +0 -50
  506. data/lib/badline/z80/extended_page.rb +0 -89
  507. data/lib/badline/z80/flags.rb +0 -37
  508. data/lib/badline/z80/interrupt_acceptance.rb +0 -46
  509. data/lib/badline/z80/jumps.rb +0 -82
  510. data/lib/badline/z80/loads.rb +0 -98
  511. data/lib/badline/z80/main_page.rb +0 -134
  512. data/lib/badline/z80/registers.rb +0 -120
  513. data/lib/badline/z80/saved_state.rb +0 -69
  514. data/lib/badline/z80/shifts.rb +0 -64
  515. data/lib/badline/z80.rb +0 -11
  516. /data/lib/badline/snapshot/{fields.rb → vice/fields.rb} +0 -0
@@ -0,0 +1,339 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "badline/chips/vic/sprite/internal_bus"
4
+ require "badline/chips/vic/sprite/shifter"
5
+ require "badline/chips/vic/sprite/saved_state"
6
+ require "badline/chips/vic/sprite/timing"
7
+
8
+ module Badline
9
+ module Chips
10
+ class VIC
11
+ # = Sprite
12
+ #
13
+ # The DMA/display state machine and the pixel sequencer for one sprite.
14
+ # The sequencer is a 24-bit shift register clocked once per pixel: the X
15
+ # comparator starts it, the expansion flip-flop gates the shift, and the
16
+ # multicolor flip-flop gates a two-bit output latch that feeds the color
17
+ # mux. Registers written mid-sprite therefore take hold from the pixel
18
+ # they reach, rather than re-decoding the row.
19
+ class Sprite
20
+ X_OFFSET = 104
21
+
22
+ # MCBASE and MC are six-bit byte counters into the sprite's 64-byte
23
+ # block. A sprite ends when MCBASE reaches exactly 63, which is why a
24
+ # mid-line $d017 write that steps it past 63 keeps it running.
25
+ LAST_MCBASE = 63
26
+ MC_MASK = 0x3f
27
+
28
+ # 24 bits at two pixels each, plus slack for a mid-sprite expansion
29
+ # change stretching the tail.
30
+ MAX_SPAN = 64
31
+
32
+ # Latch codes are the multicolor bit pairs: 0 is transparent, 1 and 3
33
+ # are the shared colors, 2 the sprite's own.
34
+ SHARED0 = 1
35
+ OWN_COLOR = 2
36
+ SHARED1 = 3
37
+
38
+ # The s-accesses reload the shift register partway through the line,
39
+ # at raster pixel 459 for sprite 0 and 16 later for each sprite after
40
+ # it. A sprite still shifting there loses the rest of its row, and the
41
+ # comparator is ignored for the twelve pixels from it, running on into
42
+ # the next line from a reload near the end of this one (the spritescan
43
+ # dumps). Its output holds the last pixel for seven, through K+6
44
+ # (spritefetchbug).
45
+ RELOAD_X = 459
46
+ RELOAD_STEP = 16
47
+ RELOAD_DEAD = 12
48
+ RELOAD_HOLD = 7
49
+
50
+ # On the line of its last row, a sprite whose DMA ended at cycle 16
51
+ # loses its display at cycle 58, so no hit from this pixel on starts
52
+ # it (spritegap3). One already shifting runs on. A region whose
53
+ # display compare falls later moves it with the compare.
54
+ DISPLAY_OFF_X = 460
55
+
56
+ include Shifter
57
+ include SavedState
58
+
59
+ attr_reader :index, :leftmost, :span, :codes,
60
+ :reload_leftmost, :reload_span, :reload_codes
61
+
62
+ def initialize(index, registers, bank, bus, model: :mos6569)
63
+ @index = index
64
+ @new_chip = model == :mos8565
65
+ @registers = registers
66
+ @bank = bank
67
+ @bus = bus
68
+ @width = bus.columns * 8
69
+ @timing = bus.timing
70
+ @bit = 1 << index
71
+ @dma = false
72
+ @display_on = false
73
+ @mcbase = 0
74
+ @mc = 0
75
+ @exp_ff = true
76
+ @crunched_mc = nil
77
+ @bits = 0
78
+ @row_ready = false
79
+ @codes = Array.new(MAX_SPAN + RELOAD_HOLD, 0)
80
+ @leftmost = 0
81
+ @span = 0
82
+ @reload_codes = Array.new(MAX_SPAN + RELOAD_HOLD, 0)
83
+ @reload_leftmost = 0
84
+ @reload_span = 0
85
+ reload = @timing.reload_x(index)
86
+ @reload_x = reload % @width
87
+ @reload_next_line = reload < @width
88
+ @sr = 0
89
+ @latch = 0
90
+ @mc_flop = false
91
+ @xe_flop = false
92
+ @first_byte_lost = false
93
+ @blind = false
94
+ @show_from = 0
95
+ end
96
+
97
+ def displaying? = @dma
98
+
99
+ # True when this line has a row for the sequencer: its own, the next
100
+ # one for a sprite that reloads late in the line, or the last one for
101
+ # a sprite that reloads at its start.
102
+ def rendering?
103
+ return true if @row_ready
104
+
105
+ @reload_next_line ? @dma && @display_on : !@prev_bits.nil?
106
+ end
107
+
108
+ def enabled? = @registers[0x15].anybits?(@bit)
109
+ def multicolor? = @registers[0x1c].anybits?(@bit)
110
+ def x_expanded? = @registers[0x1d].anybits?(@bit)
111
+ def y_expanded? = @registers[0x17].anybits?(@bit)
112
+ def priority? = @registers[0x1b].anybits?(@bit)
113
+
114
+ def x
115
+ msb = @registers[0x10].anybits?(@bit) ? 0x100 : 0
116
+ msb | @registers[index * 2]
117
+ end
118
+
119
+ # The Y comparator is eight bits wide, so the PAL lines above 255
120
+ # match the coordinates 0-55 a second time (spritey).
121
+ def y_match?(line) = (line & 0xff) == @registers[(index * 2) + 1]
122
+
123
+ def color = @registers[0x27 + index] & 0x0f
124
+
125
+ # Cycle 16: MCBASE takes MC, which the three s-accesses stepped past
126
+ # the row, but only while the expansion flip-flop is set.
127
+ def advance_mcbase
128
+ mc = @crunched_mc || ((@mc + 3) & MC_MASK)
129
+ @crunched_mc = nil
130
+ @mcbase = mc if @exp_ff && @dma
131
+ end
132
+
133
+ # The end-of-sprite compare, a column after MCBASE moves. MCBASE has
134
+ # to land on 63 exactly, so a crunched sprite steps over it and runs
135
+ # on through the rest of its block. Only the DMA stops here; the
136
+ # display waits for cycle 58.
137
+ def finish_mcbase
138
+ @dma = false if @mcbase == LAST_MCBASE
139
+ end
140
+
141
+ # Clearing MxYE sets the expansion flip-flop at once. Cleared in
142
+ # cycle 15, while the flip-flop is still reset, it also crunches MC:
143
+ # the bits step to a mix of MC and MCBASE (Bauer §3.8, VICE
144
+ # `d017_store`) that cycle 16 then hands to MCBASE.
145
+ def clear_y_expansion(crunch)
146
+ return if @exp_ff
147
+
148
+ @exp_ff = true
149
+ return unless crunch && @dma
150
+
151
+ mc = (@mc + 3) & MC_MASK
152
+ @crunched_mc = (0x2a & @mcbase & mc) | (0x15 & (@mcbase | mc))
153
+ end
154
+
155
+ # Cycle 56, after the second Y compare: MxYE inverts the expansion
156
+ # flip-flop of a sprite with DMA running.
157
+ def toggle_expansion
158
+ @exp_ff = !@exp_ff if @dma && y_expanded?
159
+ end
160
+
161
+ # Cycles 55 and 56: a Y/enable match starts the DMA and rewinds
162
+ # MCBASE, and returns true. Display is enabled separately in cycle 58,
163
+ # so the rows render from the following line on.
164
+ #
165
+ # On the VIC's side, BA falls at the sprite's own column
166
+ # (Timing#ba_column) — 55 for sprite 0 on the 6569, two later for each
167
+ # sprite after it, a column behind the window the CPU sees
168
+ # (VIC#layout_columns) — or two columns after
169
+ # this compare, whichever is later. AEC follows three columns on, and
170
+ # the first of the three s-accesses runs in the column it arrives in:
171
+ # a DMA starting on the second compare therefore loses that access for
172
+ # sprite 0, alone among the eight in following the compares
173
+ # immediately (spriteenable2).
174
+ def check_dma(line, column)
175
+ return false if @dma || !enabled? || !y_match?(line)
176
+
177
+ @first_byte_lost = column + 2 > @timing.ba_column(index)
178
+ @dma = true
179
+ @mcbase = 0
180
+ @exp_ff = true
181
+ end
182
+
183
+ # Cycle 58: MC is reloaded from MCBASE for the coming row, and a
184
+ # sprite with DMA running starts displaying only while MxE and Y still
185
+ # match — a write to either between the compares and here keeps the
186
+ # data fetch running invisibly. The display goes off only here, and
187
+ # only once the DMA has, so a DMA restarted on the line of its last
188
+ # row keeps a display that was already on (spriterestart).
189
+ def check_display(line)
190
+ @mc = @mcbase
191
+ if @dma
192
+ @display_on = true if enabled? && y_match?(line)
193
+ else
194
+ @display_on = false
195
+ end
196
+ blind_fetch if @blind && @display_on
197
+ end
198
+
199
+ # The address of the s-access that falls in phi1, the middle of the
200
+ # three, or nil when the sprite's DMA is off and the VIC idles there.
201
+ def phi1_address
202
+ (pointer * 64) + ((@mc + 1) & MC_MASK) if @dma
203
+ end
204
+
205
+ # The row fetched at the end of the previous line renders on this one.
206
+ # A lost first s-access reads back the $ff the CPU is still driving.
207
+ def start_line
208
+ @span = 0
209
+ @reload_span = 0
210
+ @prev_bits = (@bits if @row_ready && @dma)
211
+ @row_ready = false
212
+ @blind = !@dma && !@reload_next_line
213
+ @show_from = 0
214
+ lost = @first_byte_lost
215
+ @first_byte_lost = false
216
+ return unless @dma && @display_on
217
+
218
+ @bits = row_bits(@mc)
219
+ @bits |= 0xff << 16 if lost
220
+ @row_ready = true
221
+ end
222
+
223
+ # Replay the line through the pixel sequencer, filling the code buffer
224
+ # from the X match on. A log of mid-line register writes makes the
225
+ # comparator and the shift register see the values each pixel was
226
+ # drawn with.
227
+ #
228
+ # The row can be shown once before the reload and once after it, so a
229
+ # sprite moved past the beam can fire a second time on the same line
230
+ # (spritex). A match inside the dead pixels shows nothing.
231
+ def sequence(log = nil)
232
+ @span = 0
233
+ @reload_span = 0
234
+ reload_bits = reload_row
235
+ return unless @row_ready || @prev_bits || reload_bits
236
+
237
+ log = nil if log.nil? || log.empty?
238
+ @stop_x = @dma ? nil : @timing.display_off_x
239
+ comparator_hits(log).each { |start| sequence_hit(log, start, reload_bits) }
240
+ end
241
+
242
+ # The color at a raster position, for a sprite sequenced with the
243
+ # registers as they stand.
244
+ def pixel(raster_x)
245
+ code = code_at(raster_x, @leftmost, @span, @codes) ||
246
+ code_at(raster_x, @reload_leftmost, @reload_span, @reload_codes)
247
+ return nil if code.nil? || code.zero?
248
+
249
+ palette(@registers, Array.new(4, 0))[code]
250
+ end
251
+
252
+ # Fill a four-entry lookup from latch code to color, against a view of
253
+ # the color registers.
254
+ def palette(view, into)
255
+ into[SHARED0] = view[0x25] & 0x0f
256
+ into[OWN_COLOR] = view[0x27 + index] & 0x0f
257
+ into[SHARED1] = view[0x26] & 0x0f
258
+ into
259
+ end
260
+
261
+ private
262
+
263
+ # On the line whose compare started the DMA, sprites 3-7 ran their
264
+ # s-accesses before it was on, so the shift register holds what the
265
+ # VIC saw on its bus instead of a row, and a hit from the display
266
+ # turning on shows it (sbsprf24).
267
+ def blind_fetch
268
+ @bits = @bus.row(index)
269
+ @row_ready = true
270
+ @show_from = @timing.display_off_x
271
+ end
272
+
273
+ def code_at(raster_x, leftmost, span, codes)
274
+ dist = raster_x - leftmost
275
+ dist += @width if dist.negative?
276
+ codes[dist] if dist < span
277
+ end
278
+
279
+ def sequence_hit(log, start, reload_bits)
280
+ return if (@stop_x && start >= @stop_x) || start < @show_from || start < @reload_x + RELOAD_DEAD - @width
281
+
282
+ if start < @reload_x
283
+ sequence_early(log, start)
284
+ elsif start >= @reload_x + RELOAD_DEAD
285
+ sequence_reloaded(log, start, reload_bits)
286
+ end
287
+ end
288
+
289
+ def sequence_early(log, start)
290
+ bits = early_row
291
+ return unless bits && @span.zero?
292
+
293
+ @leftmost = start
294
+ cut = reload_cut(start)
295
+ @span = cut_at_reload(cut, run(log, start, @codes, bits, cut), @codes)
296
+ end
297
+
298
+ def sequence_reloaded(log, start, bits)
299
+ return unless bits && @reload_span.zero?
300
+
301
+ @reload_leftmost = start
302
+ cut = reload_cut(start)
303
+ @reload_span = cut_at_reload(cut, run(log, start, @reload_codes, bits, cut), @reload_codes)
304
+ end
305
+
306
+ # The row in the shift register ahead of the reload: this line's for
307
+ # sprites 0-2, and for the others the one their reload at the start of
308
+ # the last line brought in.
309
+ def early_row = @reload_next_line ? current_row : @prev_bits
310
+
311
+ def current_row = (@bits if @row_ready)
312
+
313
+ # The row the reload brings in. Sprites 0-2 reload late in the line
314
+ # with the next line's row, so one that fires after it shows that row
315
+ # a line early (spritegap); the others reload at the start of the line
316
+ # with the row it already has.
317
+ def reload_row
318
+ return current_row unless @reload_next_line
319
+ return unless @dma && @display_on
320
+
321
+ bits = row_bits(@mc)
322
+ bits |= 0xff << 16 if @first_byte_lost
323
+ bits
324
+ end
325
+
326
+ # The three s-accesses step MC through the sprite's block, wrapping
327
+ # within it.
328
+ def row_bits(counter)
329
+ base = pointer * 64
330
+ (@bank.peek(base + counter) << 16) |
331
+ (@bank.peek(base + ((counter + 1) & MC_MASK)) << 8) |
332
+ @bank.peek(base + ((counter + 2) & MC_MASK))
333
+ end
334
+
335
+ def pointer = @bank.peek(@registers.screen_base + 0x3f8 + index)
336
+ end
337
+ end
338
+ end
339
+ end
@@ -0,0 +1,59 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Badline
4
+ module Chips
5
+ class VIC
6
+ class Sprites
7
+ # The sprites' save_state and load_state.
8
+ module SavedState
9
+ # Whether any sprite's DMA runs, set after Sprite#restore_counters.
10
+ def update_any_dma
11
+ @any_dma = @sprites.any?(&:displaying?)
12
+ end
13
+
14
+ # The sprites, their shared bus, the collisions and the register log,
15
+ # then the line's coverage and the pixels carried into the next line,
16
+ # each as x, colour and priority.
17
+ def save_state(out)
18
+ out.boolean(@any_dma).boolean(@stopped_dma).boolean(@carried).int(@lo).int(@hi).int(@sequenced)
19
+ out.blob(@win_color).booleans(@win_priority).ints(@palette)
20
+ [@carry, @carry_next].each { |carry| carry.each { |pixels| save_carry(pixels, out) } }
21
+ @sprites.each { |sprite| sprite.save_state(out) }
22
+ @bus.save_state(out)
23
+ @collisions.save_state(out)
24
+ @log.save_state(out)
25
+ end
26
+
27
+ def load_state(input)
28
+ @any_dma = input.boolean?
29
+ @stopped_dma = input.boolean?
30
+ @carried = input.boolean?
31
+ @lo = input.int
32
+ @hi = input.int
33
+ @sequenced = input.int
34
+ input.blob_into(@win_color)
35
+ input.booleans_into(@win_priority)
36
+ input.ints_into(@palette)
37
+ [@carry, @carry_next].each { |carry| carry.each { |pixels| load_carry(pixels, input) } }
38
+ @sprites.each { |sprite| sprite.load_state(input) }
39
+ @bus.load_state(input)
40
+ @collisions.load_state(input)
41
+ @log.load_state(input)
42
+ end
43
+
44
+ private
45
+
46
+ def save_carry(pixels, out)
47
+ out.int(pixels.length / 3)
48
+ pixels.each_slice(3) { |x, color, priority| out.int(x).int(color).boolean(priority) }
49
+ end
50
+
51
+ def load_carry(pixels, input)
52
+ pixels.clear
53
+ input.int.times { pixels.push(input.int, input.int, input.boolean?) }
54
+ end
55
+ end
56
+ end
57
+ end
58
+ end
59
+ end
@@ -0,0 +1,284 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "badline/chips/vic/collisions"
4
+ require "badline/chips/vic/register_log"
5
+ require "badline/chips/vic/sprite"
6
+ require "badline/chips/vic/sprites/saved_state"
7
+
8
+ module Badline
9
+ module Chips
10
+ class VIC
11
+ class Sprites
12
+ include SavedState
13
+
14
+ # Pixels between the start of the cycle following a write and the
15
+ # point the new value shows. Each signal reaches the output on its own
16
+ # path: the colors feed the final mux, the sequencer inputs — X
17
+ # position, multicolor and expansion — sit furthest upstream, and the
18
+ # priority mux lands one pixel ahead of them.
19
+ COLOR_DELAY = 1
20
+ PRIORITY_DELAY = 6
21
+ SEQUENCER_DELAY = 7
22
+
23
+ # The beam position a CPU write in Bauer cycle 15 sees: the VIC has
24
+ # run column 13 and runs column 14, where MCBASE moves, next.
25
+ CRUNCH_X = 14 * 8
26
+
27
+ WRITE_DELAY = Array.new(2**6).tap do |delays|
28
+ (0x00..0x0e).step(2) { |reg| delays[reg] = SEQUENCER_DELAY }
29
+ [0x10, 0x1c, 0x1d].each { |reg| delays[reg] = SEQUENCER_DELAY }
30
+ delays[0x1b] = PRIORITY_DELAY
31
+ [0x25, 0x26, *0x27..0x2e].each { |reg| delays[reg] = COLOR_DELAY }
32
+ end.freeze
33
+
34
+ # The 8565's sprite sequencers take a multicolor change a pixel
35
+ # sooner than the 6569's.
36
+ WRITE_DELAY_8565 = WRITE_DELAY.dup.tap { |delays| delays[0x1c] = SEQUENCER_DELAY - 1 }.freeze
37
+
38
+ def initialize(registers, bank, width, model: :mos6569, region: Region::PAL)
39
+ @registers = registers
40
+ @write_delay = model == :mos8565 ? WRITE_DELAY_8565 : WRITE_DELAY
41
+ @bank = bank
42
+ @width = width
43
+ @bus = Sprite::InternalBus.new(bank, Sprite::Timing.new(region))
44
+ @sprites = Array.new(8) { |i| Sprite.new(i, registers, bank, @bus, model:) }
45
+ @collisions = Collisions.new(registers, width)
46
+ @hits = @collisions.hits
47
+ @win_color = Array.new(width, 0)
48
+ @win_priority = Array.new(width, false)
49
+ @palette = Array.new(4, 0)
50
+ @log = RegisterLog.new(registers)
51
+ @any_dma = false
52
+ @stopped_dma = false
53
+ @carry = Array.new(8) { [] }
54
+ @carry_next = Array.new(8) { [] }
55
+ @carried = false
56
+ @lo = width
57
+ @hi = 0
58
+ @sequenced = -1
59
+ end
60
+
61
+ def [](index) = @sprites[index]
62
+
63
+ # Pixels a sprite drew past the end of the last line land at the start
64
+ # of this one, which is when the beam reaches them.
65
+ def start_line
66
+ @bus.start_line(@registers[0x15].nonzero?)
67
+ @log.clear
68
+ @sprites.each(&:start_line)
69
+ @collisions.start_line
70
+ @sequenced = -1
71
+ @carry, @carry_next = @carry_next, @carry
72
+ @carry_next.each(&:clear)
73
+ @carried = @carry.any?(&:any?)
74
+ end
75
+
76
+ # Bauer cycle 16, two columns ahead like the compares: MCBASE takes
77
+ # MC for every sprite whose expansion flip-flop is set.
78
+ def advance_mcbase
79
+ @sprites.each(&:advance_mcbase) if @any_dma
80
+ end
81
+
82
+ # The end-of-sprite compare, a column after MCBASE moves. It is the
83
+ # only point a sprite's DMA stops, so the cached flag is settled here
84
+ # and set again by the compare that starts one.
85
+ def finish_mcbase
86
+ @stopped_dma = false
87
+ return unless @any_dma
88
+
89
+ running = @sprites.count(&:displaying?)
90
+ @sprites.each(&:finish_mcbase)
91
+ still = @sprites.count(&:displaying?)
92
+ @any_dma = still.positive?
93
+ @stopped_dma = still < running
94
+ end
95
+
96
+ # True when the last #finish_mcbase ended a DMA, so the BA columns
97
+ # have to be rebuilt without it.
98
+ def stopped_dma? = @stopped_dma
99
+
100
+ # Bauer cycle 56, after the second Y compare.
101
+ def toggle_expansion
102
+ @sprites.each(&:toggle_expansion) if @any_dma
103
+ end
104
+
105
+ # Run the Y/enable compare (Bauer cycles 55/56) for every sprite;
106
+ # returns true when any sprite turned its DMA on, so the BA columns
107
+ # can be rebuilt mid-line.
108
+ def check_dma(line, column)
109
+ return false if @registers[0x15].zero?
110
+
111
+ hit = @sprites.map { |sprite| sprite.check_dma(line, column) }.any?
112
+ @any_dma ||= hit
113
+ hit
114
+ end
115
+
116
+ # Not gated on the DMA flag, since this is where a display outlives its
117
+ # DMA. MC now points at the next row, which a sprite firing after its
118
+ # reload shows, so a line sequenced before this has to be redone.
119
+ def check_display(line)
120
+ @sprites.each { |sprite| sprite.check_display(line) }
121
+ @sequenced = -1
122
+ end
123
+
124
+ # A CPU access to a VIC register, in the cycle after the VIC's
125
+ # `column`.
126
+ def bus_data(column, value) = @bus.access(column, value)
127
+
128
+ # Not gated on the DMA flag: a row fetched at the start of the line
129
+ # still renders when cycle 16 ends the sprite.
130
+ def active? = @carried || @sprites.any?(&:rendering?)
131
+
132
+ # Record a mid-line write at the pixel where it becomes visible.
133
+ def log_change(reg, old, value, beam_x)
134
+ clear_y_expansion(old & ~value, beam_x) if reg == 0x17
135
+ delay = @write_delay[reg]
136
+ return unless delay && active?
137
+
138
+ @log.log(beam_x + delay, reg, old, value)
139
+ end
140
+
141
+ # Latch the collisions for every sprite pixel the beam has passed
142
+ # since the last fold.
143
+ def collide_upto(beam_x, mask)
144
+ upto = [beam_x, @width].min
145
+ return if upto <= @collisions.folded
146
+
147
+ sequence_line if active?
148
+ @collisions.fold(mask, upto, @lo, @hi)
149
+ end
150
+
151
+ def clear_collision(reg, beam_x) = @collisions.clear(reg, beam_x)
152
+
153
+ # End of line: fold in the pixels the beam reached since the last
154
+ # read, then paint the winners over the background. A line with no
155
+ # background to paint over — one in the vertical blank — still
156
+ # collides, so `colors` is nil there rather than absent.
157
+ def finish_line(colors, mask)
158
+ collide_upto(@width, mask)
159
+ colors ? apply(colors, mask) : clear_coverage
160
+ end
161
+
162
+ private
163
+
164
+ # A $d017 write that clears MxYE acts on the flip-flop at once, rather
165
+ # than from a column hook, and crunches in Bauer cycle 15.
166
+ def clear_y_expansion(cleared, beam_x)
167
+ crunch = beam_x == CRUNCH_X
168
+ @sprites.each do |sprite|
169
+ sprite.clear_y_expansion(crunch) if cleared.anybits?(1 << sprite.index)
170
+ end
171
+ end
172
+
173
+ # Replay the line for every sprite, filling the per-pixel coverage
174
+ # that both the fold and the composite read. A write logged later in
175
+ # the line can only move pixels past it, so re-running after the log
176
+ # grows leaves everything already folded where it was.
177
+ def sequence_line
178
+ return if @sequenced == @log.length
179
+
180
+ clear_coverage
181
+ @log.prepare
182
+ @sprites.each do |sprite|
183
+ sprite.sequence(@log)
184
+ merge(sprite)
185
+ end
186
+ @sequenced = @log.length
187
+ end
188
+
189
+ # Write one sprite's pixels into the coverage, picking up the color and
190
+ # priority registers as they stood at each pixel. The first sprite to
191
+ # claim a pixel wins (lowest index has priority); later ones only add
192
+ # their bit to it.
193
+ def merge(sprite)
194
+ bit = 1 << sprite.index
195
+ @carry_next[sprite.index].clear
196
+ @carry[sprite.index].each_slice(3) do |x, color, priority|
197
+ track(x, 1)
198
+ merge_pixel(x, color, bit, priority)
199
+ end
200
+ merge_run(sprite, sprite.leftmost, sprite.span, sprite.codes)
201
+ merge_run(sprite, sprite.reload_leftmost, sprite.reload_span, sprite.reload_codes)
202
+ end
203
+
204
+ def merge_run(sprite, leftmost, span, codes)
205
+ return if span.zero?
206
+
207
+ track(leftmost, span)
208
+ log = @log.empty? ? nil : @log
209
+ log&.rewind
210
+ merge_span(sprite, log, leftmost, span, codes)
211
+ end
212
+
213
+ def merge_span(sprite, log, leftmost, span, codes)
214
+ bit = 1 << sprite.index
215
+ palette = sprite.palette(log || @registers, @palette)
216
+ priority = (log || @registers)[0x1b].anybits?(bit)
217
+ pos = leftmost
218
+ boundary = log ? 0 : Float::INFINITY
219
+ index = 0
220
+ while index < span
221
+ if pos >= boundary
222
+ log.advance(pos)
223
+ sprite.palette(log, palette)
224
+ priority = log[0x1b].anybits?(bit)
225
+ boundary = log.next_x
226
+ end
227
+ code = codes[index]
228
+ place(pos, palette[code], bit, priority) unless code.zero?
229
+ index += 1
230
+ pos += 1
231
+ end
232
+ end
233
+
234
+ # A pixel past the end of the line belongs to the start of the next.
235
+ def place(pos, color, bit, priority)
236
+ if pos < @width
237
+ merge_pixel(pos, color, bit, priority)
238
+ else
239
+ @carry_next[bit.bit_length - 1].push(pos - @width, color, priority)
240
+ end
241
+ end
242
+
243
+ def track(left, span)
244
+ right = left + span
245
+ if right > @width
246
+ @lo = 0
247
+ @hi = @width
248
+ else
249
+ @lo = left if left < @lo
250
+ @hi = right if right > @hi
251
+ end
252
+ end
253
+
254
+ def merge_pixel(pos, color, bit, priority)
255
+ bits = @hits[pos]
256
+ if bits.zero?
257
+ @win_color[pos] = color
258
+ @win_priority[pos] = priority
259
+ end
260
+ @hits[pos] = bits | bit
261
+ end
262
+
263
+ def apply(colors, mask)
264
+ pos = @lo
265
+ while pos < @hi
266
+ unless @hits[pos].zero?
267
+ colors[pos] = @win_color[pos] unless @win_priority[pos] && mask[pos]
268
+ @hits[pos] = 0
269
+ end
270
+ pos += 1
271
+ end
272
+ @lo = @width
273
+ @hi = 0
274
+ end
275
+
276
+ def clear_coverage
277
+ @hits.fill(0, @lo, @hi - @lo) if @hi > @lo
278
+ @lo = @width
279
+ @hi = 0
280
+ end
281
+ end
282
+ end
283
+ end
284
+ end