badline 0.13.0 → 0.15.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 (286) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +72 -1
  3. data/CLAUDE.md +42 -12
  4. data/CONTRIBUTING.md +4 -3
  5. data/README.md +76 -13
  6. data/doc/media.md +2 -1
  7. data/doc/pinned-behaviour.md +174 -0
  8. data/doc/snapshot-format.md +1197 -0
  9. data/doc/snapshots.md +90 -40
  10. data/lib/badline/audio/bare_player.rb +46 -17
  11. data/lib/badline/audio/machine_player.rb +6 -10
  12. data/lib/badline/audio/renderer.rb +32 -21
  13. data/lib/badline/c128/banks.rb +7 -6
  14. data/lib/badline/c128/bus/keyed_state.rb +45 -0
  15. data/lib/badline/c128/bus.rb +36 -4
  16. data/lib/badline/c128/drives.rb +6 -25
  17. data/lib/badline/c128/keyed_modules.rb +117 -0
  18. data/lib/badline/c128/keyed_state.rb +91 -0
  19. data/lib/badline/c128/keys.rb +9 -0
  20. data/lib/badline/c128/mmu.rb +9 -8
  21. data/lib/badline/c128/mmu_pages.rb +27 -0
  22. data/lib/badline/c128/modes.rb +19 -9
  23. data/lib/badline/c128/z80_turns.rb +1 -1
  24. data/lib/badline/c128.rb +8 -8
  25. data/lib/badline/c64/attachments.rb +0 -44
  26. data/lib/badline/c64/banked_ram/plus256k.rb +18 -10
  27. data/lib/badline/c64/banked_ram/plus60k.rb +8 -6
  28. data/lib/badline/c64/banked_ram/unexpanded.rb +2 -2
  29. data/lib/badline/c64/bus/debug_register.rb +32 -0
  30. data/lib/badline/c64/bus/extra_sids.rb +80 -0
  31. data/lib/badline/c64/bus/keyed_state.rb +41 -0
  32. data/lib/badline/c64/bus.rb +3 -1
  33. data/lib/badline/c64/cartridge/action_replay.rb +6 -15
  34. data/lib/badline/c64/cartridge/comal80.rb +1 -0
  35. data/lib/badline/c64/cartridge/dinamic.rb +3 -1
  36. data/lib/badline/c64/cartridge/easy_flash.rb +0 -15
  37. data/lib/badline/c64/cartridge/epyx_fastload.rb +0 -8
  38. data/lib/badline/c64/cartridge/final_cartridge3.rb +1 -12
  39. data/lib/badline/c64/cartridge/flash.rb +3 -3
  40. data/lib/badline/c64/cartridge/fun_play.rb +2 -1
  41. data/lib/badline/c64/cartridge/g_mod2.rb +0 -12
  42. data/lib/badline/c64/cartridge/game_system.rb +2 -1
  43. data/lib/badline/c64/cartridge/geo_ram.rb +0 -16
  44. data/lib/badline/c64/cartridge/kcs_power.rb +0 -8
  45. data/lib/badline/c64/cartridge/keyed_state.rb +514 -0
  46. data/lib/badline/c64/cartridge/magic_desk.rb +2 -1
  47. data/lib/badline/c64/cartridge/ocean.rb +1 -0
  48. data/lib/badline/c64/cartridge/pagefox.rb +1 -10
  49. data/lib/badline/c64/cartridge/retro_replay.rb +0 -8
  50. data/lib/badline/c64/cartridge/rgcd.rb +2 -11
  51. data/lib/badline/c64/cartridge/super_games.rb +3 -12
  52. data/lib/badline/c64/cartridge/zaxxon.rb +1 -16
  53. data/lib/badline/c64/cartridge.rb +4 -30
  54. data/lib/badline/c64/kernal_traps.rb +28 -25
  55. data/lib/badline/c64/keyed_modules.rb +115 -0
  56. data/lib/badline/c64/keyed_state.rb +68 -0
  57. data/lib/badline/c64/reu/dma.rb +0 -4
  58. data/lib/badline/c64/reu/keyed_state.rb +140 -0
  59. data/lib/badline/c64/reu/ram.rb +0 -15
  60. data/lib/badline/c64/reu.rb +1 -3
  61. data/lib/badline/c64/true_drives.rb +56 -5
  62. data/lib/badline/c64.rb +7 -3
  63. data/lib/badline/chips/cia/interrupt_register.rb +0 -17
  64. data/lib/badline/chips/cia/keyed_parts.rb +213 -0
  65. data/lib/badline/chips/cia/serial.rb +0 -4
  66. data/lib/badline/chips/cia/time_of_day.rb +0 -22
  67. data/lib/badline/chips/cia/timer.rb +0 -20
  68. data/lib/badline/chips/cia.rb +2 -2
  69. data/lib/badline/chips/cpu/core.rb +18 -2
  70. data/lib/badline/chips/cpu/keyed_state.rb +177 -0
  71. data/lib/badline/chips/cpu/microcode.rb +16 -0
  72. data/lib/badline/chips/cpu/port.rb +20 -3
  73. data/lib/badline/chips/cpu.rb +5 -0
  74. data/lib/badline/chips/sid/decimator.rb +10 -8
  75. data/lib/badline/chips/sid/envelope.rb +1 -6
  76. data/lib/badline/chips/sid/filter.rb +0 -14
  77. data/lib/badline/chips/sid/keyed_parts.rb +286 -0
  78. data/lib/badline/chips/sid/voice.rb +0 -10
  79. data/lib/badline/chips/sid/waveform.rb +0 -2
  80. data/lib/badline/chips/sid.rb +1 -2
  81. data/lib/badline/chips/ted/beam.rb +118 -0
  82. data/lib/badline/chips/ted/fetches.rb +199 -0
  83. data/lib/badline/chips/ted/keyed_state.rb +156 -0
  84. data/lib/badline/chips/ted/modes.rb +134 -0
  85. data/lib/badline/chips/ted/palette.rb +43 -0
  86. data/lib/badline/chips/ted/picture.rb +182 -0
  87. data/lib/badline/chips/ted/sound/output.rb +86 -0
  88. data/lib/badline/chips/ted/sound.rb +284 -0
  89. data/lib/badline/chips/ted/timers.rb +66 -0
  90. data/lib/badline/chips/ted.rb +190 -0
  91. data/lib/badline/chips/tia.rb +61 -0
  92. data/lib/badline/chips/vdc/keyed_parts.rb +105 -0
  93. data/lib/badline/chips/vdc/painter.rb +0 -12
  94. data/lib/badline/chips/vdc/raster.rb +0 -16
  95. data/lib/badline/chips/vdc/window.rb +0 -9
  96. data/lib/badline/chips/vdc.rb +1 -2
  97. data/lib/badline/chips/via/control_lines.rb +0 -15
  98. data/lib/badline/chips/via/interrupt_register.rb +0 -9
  99. data/lib/badline/chips/via/keyed_parts.rb +163 -0
  100. data/lib/badline/chips/via/shift_register.rb +0 -14
  101. data/lib/badline/chips/via/timer1.rb +0 -13
  102. data/lib/badline/chips/via/timer2.rb +0 -18
  103. data/lib/badline/chips/via.rb +1 -2
  104. data/lib/badline/chips/vic/border_mask.rb +0 -10
  105. data/lib/badline/chips/vic/collisions.rb +0 -11
  106. data/lib/badline/chips/vic/color_patches.rb +0 -11
  107. data/lib/badline/chips/vic/display_state.rb +0 -19
  108. data/lib/badline/chips/vic/graphics_shifter.rb +0 -4
  109. data/lib/badline/chips/vic/keyed_parts.rb +228 -0
  110. data/lib/badline/chips/vic/keyed_sprites.rb +225 -0
  111. data/lib/badline/chips/vic/keyed_state.rb +102 -0
  112. data/lib/badline/chips/vic/register_log.rb +0 -18
  113. data/lib/badline/chips/vic/registers.rb +0 -9
  114. data/lib/badline/chips/vic/sequencer.rb +0 -2
  115. data/lib/badline/chips/vic/sprite/internal_bus.rb +0 -11
  116. data/lib/badline/chips/vic/sprite.rb +0 -2
  117. data/lib/badline/chips/vic/sprites.rb +0 -3
  118. data/lib/badline/chips/vic.rb +6 -2
  119. data/lib/badline/chips/vic_i/keyed_parts.rb +142 -0
  120. data/lib/badline/chips/vic_i/painter.rb +0 -4
  121. data/lib/badline/chips/vic_i/sound.rb +38 -46
  122. data/lib/badline/chips/vic_i.rb +2 -2
  123. data/lib/badline/chips/z80/core.rb +2 -2
  124. data/lib/badline/chips/z80/keyed_state.rb +73 -0
  125. data/lib/badline/chips.rb +10 -0
  126. data/lib/badline/core.rb +4 -21
  127. data/lib/badline/drives/datasette/keyed_state.rb +43 -0
  128. data/lib/badline/drives/datasette.rb +62 -12
  129. data/lib/badline/drives/engine/core.rb +3 -0
  130. data/lib/badline/drives/engine/serial_lines.rb +4 -7
  131. data/lib/badline/drives/model1541/bus.rb +0 -13
  132. data/lib/badline/drives/model1541/disk.rb +9 -8
  133. data/lib/badline/drives/model1541/keyed_state.rb +251 -0
  134. data/lib/badline/drives/model1541/mechanism.rb +0 -2
  135. data/lib/badline/drives/model1541.rb +1 -2
  136. data/lib/badline/drives/model1571/bus.rb +0 -13
  137. data/lib/badline/drives/model1571/keyed_state.rb +108 -0
  138. data/lib/badline/drives/model1571/wd1770.rb +0 -14
  139. data/lib/badline/drives/model1571.rb +1 -2
  140. data/lib/badline/drives/model1581/bus.rb +0 -13
  141. data/lib/badline/drives/model1581/disk.rb +0 -4
  142. data/lib/badline/drives/model1581/keyed_state.rb +280 -0
  143. data/lib/badline/drives/model1581/mechanism.rb +0 -21
  144. data/lib/badline/drives/model1581/slot.rb +0 -18
  145. data/lib/badline/drives/model1581/wd1772.rb +0 -2
  146. data/lib/badline/drives/model1581.rb +2 -2
  147. data/lib/badline/drives.rb +8 -0
  148. data/lib/badline/frontend/app.rb +3 -3
  149. data/lib/badline/frontend/boot.rb +4 -2
  150. data/lib/badline/frontend/controls.rb +25 -5
  151. data/lib/badline/frontend/drive_led.rb +44 -21
  152. data/lib/badline/frontend/keys.rb +14 -2
  153. data/lib/badline/frontend/media_slots.rb +11 -1
  154. data/lib/badline/frontend/menu/menu_dialogs.rb +4 -3
  155. data/lib/badline/frontend/menu/menu_media.rb +13 -3
  156. data/lib/badline/frontend/menu/menu_pages.rb +8 -8
  157. data/lib/badline/frontend/menu/menu_ports.rb +13 -1
  158. data/lib/badline/frontend/screen.rb +33 -12
  159. data/lib/badline/frontend/screens.rb +2 -1
  160. data/lib/badline/frontend/snapshots.rb +12 -1
  161. data/lib/badline/frontend/sound.rb +4 -4
  162. data/lib/badline/frontend/timeline.rb +43 -12
  163. data/lib/badline/input/matrix_typist.rb +98 -0
  164. data/lib/badline/input.rb +1 -0
  165. data/lib/badline/kernal_trap/chrout.rb +45 -0
  166. data/lib/badline/kernal_trap/dos/channel.rb +0 -27
  167. data/lib/badline/kernal_trap/dos/channels.rb +0 -25
  168. data/lib/badline/kernal_trap/dos/memory.rb +0 -9
  169. data/lib/badline/kernal_trap/dos/status.rb +0 -8
  170. data/lib/badline/kernal_trap/dos/write_file.rb +0 -18
  171. data/lib/badline/kernal_trap/dos.rb +0 -19
  172. data/lib/badline/kernal_trap/file_routine.rb +4 -4
  173. data/lib/badline/kernal_trap/keyed_state.rb +150 -0
  174. data/lib/badline/kernal_trap/layout.rb +75 -11
  175. data/lib/badline/kernal_trap/load.rb +14 -16
  176. data/lib/badline/kernal_trap/routine.rb +12 -0
  177. data/lib/badline/kernal_trap/save.rb +8 -18
  178. data/lib/badline/kernal_trap/serial.rb +1 -17
  179. data/lib/badline/kernal_trap.rb +2 -0
  180. data/lib/badline/machine/keyboard_buffer.rb +49 -0
  181. data/lib/badline/machine/settling.rb +29 -0
  182. data/lib/badline/machine.rb +29 -6
  183. data/lib/badline/media/boot_disk.rb +14 -2
  184. data/lib/badline/media/extensions.rb +10 -0
  185. data/lib/badline/media/plus4_media.rb +68 -0
  186. data/lib/badline/media/program.rb +20 -6
  187. data/lib/badline/media.rb +33 -12
  188. data/lib/badline/memory.rb +3 -6
  189. data/lib/badline/options/family.rb +6 -2
  190. data/lib/badline/options/help.rb +66 -16
  191. data/lib/badline/options/table.rb +17 -2
  192. data/lib/badline/options/validation.rb +26 -2
  193. data/lib/badline/options.rb +42 -9
  194. data/lib/badline/plus4/acia.rb +45 -0
  195. data/lib/badline/plus4/bus.rb +375 -0
  196. data/lib/badline/plus4/cassette_sense.rb +21 -0
  197. data/lib/badline/plus4/cpu.rb +10 -0
  198. data/lib/badline/plus4/cpu_port.rb +93 -0
  199. data/lib/badline/plus4/keyboard_port.rb +76 -0
  200. data/lib/badline/plus4/keyed_modules.rb +99 -0
  201. data/lib/badline/plus4/keyed_parts.rb +143 -0
  202. data/lib/badline/plus4/keyed_state.rb +41 -0
  203. data/lib/badline/plus4/model.rb +38 -0
  204. data/lib/badline/plus4/user_port.rb +31 -0
  205. data/lib/badline/plus4.rb +240 -0
  206. data/lib/badline/roms/README +38 -0
  207. data/lib/badline/roms/plus4/basic.rom +0 -0
  208. data/lib/badline/roms/plus4/function-high.rom +0 -0
  209. data/lib/badline/roms/plus4/function-low.rom +0 -0
  210. data/lib/badline/roms/plus4/kernal-ntsc.rom +0 -0
  211. data/lib/badline/roms/plus4/kernal-pal.rom +0 -0
  212. data/lib/badline/snapshot/c128_setup.rb +9 -20
  213. data/lib/badline/snapshot/c64_setup.rb +28 -24
  214. data/lib/badline/snapshot/container.rb +6 -0
  215. data/lib/badline/snapshot/field_doc/markdown.rb +113 -0
  216. data/lib/badline/snapshot/field_doc.rb +148 -0
  217. data/lib/badline/snapshot/image.rb +143 -28
  218. data/lib/badline/snapshot/keyed/cursor.rb +40 -0
  219. data/lib/badline/snapshot/keyed/modules.rb +66 -0
  220. data/lib/badline/snapshot/keyed/reader.rb +188 -0
  221. data/lib/badline/snapshot/keyed/recorder.rb +88 -0
  222. data/lib/badline/snapshot/keyed/report.rb +26 -0
  223. data/lib/badline/snapshot/keyed/writer.rb +55 -0
  224. data/lib/badline/snapshot/keyed.rb +64 -0
  225. data/lib/badline/snapshot/machine_state.rb +17 -64
  226. data/lib/badline/snapshot/plus4_setup.rb +39 -0
  227. data/lib/badline/snapshot/storage_setup.rb +29 -34
  228. data/lib/badline/snapshot/vic20_setup.rb +6 -15
  229. data/lib/badline/snapshot/vice/reu1764.rb +2 -2
  230. data/lib/badline/snapshot/vice.rb +7 -31
  231. data/lib/badline/snapshot.rb +48 -20
  232. data/lib/badline/storage/crt_file.rb +3 -1
  233. data/lib/badline/storage/g64_image.rb +9 -4
  234. data/lib/badline/storage/tap.rb +57 -20
  235. data/lib/badline/support/checkpoint.rb +3 -2
  236. data/lib/badline/support/plus4_checkpoint.rb +42 -0
  237. data/lib/badline/version.rb +1 -1
  238. data/lib/badline/vic20/attachments.rb +3 -0
  239. data/lib/badline/vic20/bus/keyed_state.rb +52 -0
  240. data/lib/badline/vic20/bus.rb +1 -2
  241. data/lib/badline/vic20/keyed_modules.rb +73 -0
  242. data/lib/badline/vic20/keyed_state.rb +45 -0
  243. data/lib/badline/vic20.rb +5 -5
  244. data/lib/badline.rb +1 -0
  245. metadata +75 -42
  246. data/lib/badline/c128/bus/saved_state.rb +0 -53
  247. data/lib/badline/c128/saved_state.rb +0 -117
  248. data/lib/badline/c64/bus/saved_state.rb +0 -68
  249. data/lib/badline/c64/cartridge/flash/saved_state.rb +0 -40
  250. data/lib/badline/c64/cartridge/retro_replay/saved_state.rb +0 -47
  251. data/lib/badline/c64/cartridge/saved_state.rb +0 -63
  252. data/lib/badline/c64/reu/dma/saved_state.rb +0 -52
  253. data/lib/badline/c64/reu/saved_state.rb +0 -55
  254. data/lib/badline/c64/saved_state.rb +0 -112
  255. data/lib/badline/chips/cia/saved_state.rb +0 -49
  256. data/lib/badline/chips/cia/serial/saved_state.rb +0 -45
  257. data/lib/badline/chips/cpu/saved_state.rb +0 -117
  258. data/lib/badline/chips/sid/envelope/saved_state.rb +0 -70
  259. data/lib/badline/chips/sid/filter/saved_state.rb +0 -42
  260. data/lib/badline/chips/sid/saved_state.rb +0 -52
  261. data/lib/badline/chips/sid/waveform/saved_state.rb +0 -68
  262. data/lib/badline/chips/vdc/saved_state.rb +0 -36
  263. data/lib/badline/chips/via/saved_state.rb +0 -44
  264. data/lib/badline/chips/vic/graphics_shifter/saved_state.rb +0 -40
  265. data/lib/badline/chips/vic/saved_state.rb +0 -82
  266. data/lib/badline/chips/vic/sequencer/saved_state.rb +0 -91
  267. data/lib/badline/chips/vic/sprite/saved_state.rb +0 -66
  268. data/lib/badline/chips/vic/sprites/saved_state.rb +0 -59
  269. data/lib/badline/chips/vic_i/painter/saved_state.rb +0 -40
  270. data/lib/badline/chips/vic_i/saved_state.rb +0 -55
  271. data/lib/badline/chips/z80/saved_state.rb +0 -71
  272. data/lib/badline/chrout_trap.rb +0 -43
  273. data/lib/badline/debug_register.rb +0 -28
  274. data/lib/badline/drives/datasette/saved_state.rb +0 -37
  275. data/lib/badline/drives/model1541/disk/saved_state.rb +0 -88
  276. data/lib/badline/drives/model1541/mechanism/saved_state.rb +0 -72
  277. data/lib/badline/drives/model1541/saved_state.rb +0 -44
  278. data/lib/badline/drives/model1571/saved_state.rb +0 -47
  279. data/lib/badline/drives/model1581/disk/saved_state.rb +0 -89
  280. data/lib/badline/drives/model1581/saved_state.rb +0 -42
  281. data/lib/badline/drives/model1581/wd1772/saved_state.rb +0 -68
  282. data/lib/badline/keyboard_buffer.rb +0 -33
  283. data/lib/badline/snapshot/setup.rb +0 -31
  284. data/lib/badline/snapshot/state.rb +0 -179
  285. data/lib/badline/vic20/bus/saved_state.rb +0 -35
  286. data/lib/badline/vic20/saved_state.rb +0 -110
@@ -12,9 +12,9 @@ module Badline
12
12
  # Either opens the window for any media, or with --headless or
13
13
  # --audio-out plays or renders a .sid tune without one. `sid`, given
14
14
  # first, plays .sid tunes and directories of them in the terminal.
15
- # `c64`, `vic20` or `c128`, given first, names the machine family, and
16
- # without one the family is the C64, or the C128 for a program that loads
17
- # where C128 BASIC starts. The native build adds --no-sound and
15
+ # `c64`, `vic20`, `c128` or `plus4`, given first, names the machine
16
+ # family, and without one the family is the C64, or the C128 for a program
17
+ # that loads where C128 BASIC starts. The native build adds --no-sound and
18
18
  # --version, and badline-ruby adds --disable-jit.
19
19
  #
20
20
  # TABLE lists the options, and both the parser and the help read it. It
@@ -48,6 +48,13 @@ module Badline
48
48
  "32k" => :"32k", "all" => :all
49
49
  }.freeze
50
50
 
51
+ # The Plus/4's RAM sizes as --ram names them, in K.
52
+ PLUS4_RAM = { "16k" => 16, "32k" => 32, "64k" => 64 }.freeze
53
+
54
+ # The Plus/4's cartridge slots, by the options that fill them and
55
+ # xplus4's names (Media::Plus4Media::SLOTS).
56
+ CARTRIDGE_SLOTS = { "--c1lo" => "c1lo", "--c1hi" => "c1hi", "--c2lo" => "c2lo", "--c2hi" => "c2hi" }.freeze
57
+
51
58
  # A Float as OptionParser took one: digits with an optional sign,
52
59
  # fraction and exponent.
53
60
  DECIMAL = /\A[-+]?(\d+(\.\d+)?|\.\d+)([eE][-+]?\d+)?\z/
@@ -57,14 +64,22 @@ module Badline
57
64
  attr_reader :program, :media_path, :tune_paths, :subtune, :sid_model, :reu, :frames, :screenshot, :save_snapshot,
58
65
  :audio_out, :seconds, :songlengths, :filter_chunk, :timeline
59
66
 
60
- # The machine family to build (Badline::Machine), :c64, :vic20 or
61
- # :c128.
67
+ # The machine family to build (Badline::Machine), :c64, :vic20, :c128
68
+ # or :plus4.
62
69
  attr_reader :family
63
70
 
64
71
  # The VIC-20's RAM expansion --ram names, a key of
65
72
  # Vic20::Bus::RAM_CONFIGURATIONS, or nil without --ram.
66
73
  attr_reader :ram
67
74
 
75
+ # The Plus/4's RAM in K --ram names, one of PLUS4_RAM's, or nil without
76
+ # --ram.
77
+ attr_reader :ram_kb
78
+
79
+ # The Plus/4's cartridge ROM images --c1lo, --c1hi, --c2lo and --c2hi
80
+ # name, by slot: { "c1lo" => path }.
81
+ attr_reader :cartridges
82
+
68
83
  def self.parse(argv, native: false) = new(native:).parse(argv)
69
84
 
70
85
  # The native build plays the SID in the window unless --no-sound says
@@ -84,6 +99,8 @@ module Badline
84
99
  @models = []
85
100
  @reu = nil
86
101
  @ram = nil
102
+ @ram_kb = nil
103
+ @cartridges = {}
87
104
  @autostart = true
88
105
  @writable = false
89
106
  @sound = native
@@ -94,6 +111,7 @@ module Badline
94
111
  @true_drive = false
95
112
  @screenshot = ""
96
113
  @save_snapshot = ""
114
+ @vice = false
97
115
  @timeline = []
98
116
  @jit = true
99
117
  @help = false
@@ -171,11 +189,14 @@ module Badline
171
189
 
172
190
  def true_drive? = @true_drive
173
191
 
192
+ # Whether --save-snapshot adds VICE's modules, so x64sc opens it.
193
+ def vice? = @vice
194
+
174
195
  def jit? = @jit
175
196
 
176
197
  # The name of the model to build: a C64 Badline::Model names, a VIC-20
177
- # of VIC20_MODELS or a C128 of C128_MODELS. --ntsc is short for --model
178
- # ntsc.
198
+ # of VIC20_MODELS, a C128 of C128_MODELS or a Plus/4 of PLUS4_MODELS.
199
+ # --ntsc is short for --model ntsc.
179
200
  def model = @models.first || family_models.first
180
201
 
181
202
  # Whether --c64 asks the C128 to start in C64 mode, holding C= at
@@ -197,7 +218,8 @@ module Badline
197
218
  # A .sid on its own plays in the SID player, as `sid` would, unless an
198
219
  # option of the emulator's window asks for the machine.
199
220
  def play_lone_tune
200
- return if @sid_command || @family == :vic20 || headless? || !@window_only.empty? || @media_path.nil?
221
+ return if @sid_command || %i[vic20 plus4].include?(@family) || headless? || !@window_only.empty?
222
+ return if @media_path.nil?
201
223
 
202
224
  @sid_command = File.extname(@media_path).casecmp?(".sid")
203
225
  end
@@ -250,7 +272,8 @@ module Badline
250
272
  when "--sid" then @sid_model = sid_model_for(value)
251
273
  when "--model" then @models << value
252
274
  when "--reu" then @reu = reu_size(value)
253
- when "--ram" then @ram = ram_configuration(value)
275
+ when "--ram" then ram_option(value)
276
+ when "--c1lo", "--c1hi", "--c2lo", "--c2hi" then @cartridges[CARTRIDGE_SLOTS.fetch(name)] = value
254
277
  when "--frames" then @frames = number(flag, value)
255
278
  when "--screenshot" then @screenshot = value
256
279
  when "--save-snapshot" then @save_snapshot = value
@@ -263,6 +286,15 @@ module Badline
263
286
  end
264
287
  end
265
288
 
289
+ # --ram names the VIC-20's RAM expansion, or the Plus/4's RAM.
290
+ def ram_option(value)
291
+ if @family == :plus4
292
+ @ram_kb = plus4_ram(value)
293
+ else
294
+ @ram = ram_configuration(value)
295
+ end
296
+ end
297
+
266
298
  def switch(name)
267
299
  case name
268
300
  when "--no-autostart" then @autostart = false
@@ -272,6 +304,7 @@ module Badline
272
304
  when "--no-vsync" then @vsync = false
273
305
  when "--verbose" then @verbose = true
274
306
  when "--true-drive" then @true_drive = true
307
+ when "--vice" then @vice = true
275
308
  when "--ntsc" then @models << "ntsc"
276
309
  when "--c64" then @c64_mode = true
277
310
  when "--unpaced" then @paced = false
@@ -0,0 +1,45 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Badline
4
+ class Plus4
5
+ # The Plus/4's 6551 ACIA at $FD00-$FD0F, its four registers repeated,
6
+ # as a stub with nothing on the line: it never receives, its
7
+ # transmitter is always empty, and it never interrupts. The command and
8
+ # control registers keep what is written to them, and a write to the
9
+ # status register, a programmed reset, clears the command register's
10
+ # low five bits.
11
+ class ACIA
12
+ STATUS = 0x10
13
+
14
+ attr_reader :command, :control
15
+
16
+ def initialize
17
+ reset!
18
+ end
19
+
20
+ def reset!
21
+ @command = 0
22
+ @control = 0
23
+ end
24
+
25
+ def irq? = false
26
+
27
+ def peek(addr)
28
+ case addr & 0x03
29
+ when 1 then STATUS
30
+ when 2 then @command
31
+ when 3 then @control
32
+ else 0
33
+ end
34
+ end
35
+
36
+ def poke(addr, value)
37
+ case addr & 0x03
38
+ when 1 then @command &= 0xe0
39
+ when 2 then @command = value
40
+ when 3 then @control = value
41
+ end
42
+ end
43
+ end
44
+ end
45
+ end
@@ -0,0 +1,375 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Badline
4
+ class Plus4
5
+ # The CPU's bus:
6
+ #
7
+ # $0000-$0001 - the 7501's port (CPUPort), over RAM
8
+ # $0002-$7FFF - RAM
9
+ # $8000-$BFFF - the low ROM bank, or RAM
10
+ # $C000-$FCFF - the high ROM bank, or RAM; $FC00-$FCFF is always the
11
+ # KERNAL's while ROM is selected
12
+ # $FD00-$FD0F - the 6551 ACIA (Plus/4 only)
13
+ # $FD10-$FD1F - the user port's 6529 (Plus/4 only)
14
+ # $FD30-$FD3F - the keyboard's 6529 (KeyboardPort)
15
+ # $FDD0-$FDDF - the ROM bank latch
16
+ # $FF00-$FF1F - the TED
17
+ # $FF3E/$FF3F - a write selects ROM or RAM at $8000-$FFFF
18
+ # $FF40-$FFFF - the high ROM bank, or RAM
19
+ #
20
+ # A write to $FF3E selects ROM and one to $FF3F RAM, everywhere in
21
+ # $8000-$FFFF but $FD00-$FF3F, which holds I/O either way. ROM is
22
+ # selected at power-on and reset. A write to ROM goes to the RAM under
23
+ # it. The ROM bank latch takes the address written to, not the byte:
24
+ # bits 0-1 pick the low bank and bits 2-3 the high bank, 0 for BASIC
25
+ # and the KERNAL, 1 for the Plus/4's function ROMs, 2 and 3 for the two
26
+ # cartridges.
27
+ #
28
+ # RAM smaller than 64K repeats through the 64K, as the C16's 16K does
29
+ # without A14 and A15. Where nothing answers, a read finds the last byte
30
+ # on the bus.
31
+ #
32
+ # The TED fetches through the same banks: the video matrix and the
33
+ # attributes from ROM or RAM at $8000-$FFFF as the CPU sees it, with no
34
+ # I/O in the way (#ted_fetch), and character and bitmap data from RAM,
35
+ # or from ROM with $FF12 bit 2 set, where below $8000 and in an empty
36
+ # bank it finds the last byte on the bus (#ted_pattern_fetch, Plus/4
37
+ # openmem). Each byte it fetches is left on the bus.
38
+ class Bus
39
+ include Addressable
40
+
41
+ RAM_POWER_ON = 0x00
42
+
43
+ # RAM through a size it repeats at.
44
+ class MirroredRAM
45
+ def initialize(ram, mask)
46
+ @ram = ram
47
+ @mask = mask
48
+ end
49
+
50
+ def peek(addr) = @ram.peek(addr & @mask)
51
+
52
+ def poke(addr, value)
53
+ @ram.poke(addr & @mask, value)
54
+ end
55
+ end
56
+
57
+ # The last byte the CPU moved, which an empty spot reads back.
58
+ class OpenBus
59
+ def initialize(bus)
60
+ @bus = bus
61
+ end
62
+
63
+ def peek(_addr) = @bus.data
64
+ def poke(_addr, _value); end
65
+ end
66
+
67
+ # Page 0: the port at $00 and $01, RAM above it.
68
+ class ZeroPage
69
+ def initialize(port, ram)
70
+ @port = port
71
+ @ram = ram
72
+ end
73
+
74
+ def peek(addr) = addr < 2 ? @port.peek(addr) : @ram.peek(addr)
75
+
76
+ def poke(addr, value)
77
+ return @port.poke(addr, value) if addr < 2
78
+
79
+ @ram.poke(addr, value)
80
+ end
81
+ end
82
+
83
+ # Page $FD: the ACIA, the user port, the keyboard port and the ROM
84
+ # bank latch, by the address's bits 4-7. +acia+ and +user_port+ are
85
+ # nil where the machine has none, and +cassette_sense+ answers in the
86
+ # user port's place on a machine without one (CassetteSense).
87
+ class IOPage
88
+ def initialize(keyboard_port:, acia: nil, user_port: nil, cassette_sense: nil)
89
+ @bus = nil
90
+ @acia = acia
91
+ @user_port = user_port
92
+ @cassette_sense = cassette_sense
93
+ @keyboard_port = keyboard_port
94
+ end
95
+
96
+ # The bus whose last byte an empty spot reads and whose bank latch
97
+ # a write to $FDD0-$FDDF sets.
98
+ def connect(bus)
99
+ @bus = bus
100
+ end
101
+
102
+ def peek(addr)
103
+ case addr & 0xf0
104
+ when 0x00 then @acia ? @acia.peek(addr) : @bus.data
105
+ when 0x10 then user_port_peek(addr)
106
+ when 0x30 then @keyboard_port.peek(addr)
107
+ else @bus.data
108
+ end
109
+ end
110
+
111
+ def poke(addr, value)
112
+ case addr & 0xf0
113
+ when 0x00 then @acia&.poke(addr, value)
114
+ when 0x10 then @user_port&.poke(addr, value)
115
+ when 0x30 then @keyboard_port.poke(addr, value)
116
+ when 0xd0 then @bus.select_banks(addr)
117
+ end
118
+ end
119
+
120
+ private
121
+
122
+ def user_port_peek(addr)
123
+ return @user_port.peek(addr) if @user_port
124
+ return @cassette_sense.read(@bus.data) if @cassette_sense
125
+
126
+ @bus.data
127
+ end
128
+ end
129
+
130
+ # VICE's debug cartridge register at $FDCF, a spot in the I/O page
131
+ # that nothing answers (plus4world forum post 31417). A write there
132
+ # calls the handler with the byte, the exit code the VICE testprogs
133
+ # report. Every other access falls through to the I/O page.
134
+ class DebugRegister
135
+ ADDRESS = 0xfdcf
136
+
137
+ def initialize(io, &handler)
138
+ @io = io
139
+ @handler = handler
140
+ end
141
+
142
+ def peek(addr) = @io.peek(addr)
143
+
144
+ def poke(addr, value)
145
+ return @handler.call(value) if addr == ADDRESS
146
+
147
+ @io.poke(addr, value)
148
+ end
149
+ end
150
+
151
+ # Page $FE with a 1551's TCBM interface plugged in: the 6523 TIA
152
+ # that talks to drive 9 at $FEC0-$FEDF and drive 8's at $FEE0-$FEFF.
153
+ # The rest of the page is open.
154
+ class TCBMPage
155
+ def initialize(bus, drive8:, drive9:)
156
+ @bus = bus
157
+ @drive8 = drive8
158
+ @drive9 = drive9
159
+ end
160
+
161
+ def peek(addr)
162
+ return @bus.data if addr < 0xfec0
163
+
164
+ addr < 0xfee0 ? @drive9.peek(addr) : @drive8.peek(addr)
165
+ end
166
+
167
+ def poke(addr, value)
168
+ return if addr < 0xfec0
169
+
170
+ addr < 0xfee0 ? @drive9.poke(addr, value) : @drive8.poke(addr, value)
171
+ end
172
+ end
173
+
174
+ # Page $FF: the TED in $FF00-$FF3F, its two phantom registers, and
175
+ # ROM or RAM above.
176
+ class TopPage
177
+ def initialize(bus, ted)
178
+ @bus = bus
179
+ @ted = ted
180
+ end
181
+
182
+ def peek(addr)
183
+ return @ted.peek(addr) if addr < 0xff40
184
+
185
+ @bus.top_memory.peek(addr)
186
+ end
187
+
188
+ def poke(addr, value)
189
+ return @bus.ram_view.poke(addr, value) if addr >= 0xff40
190
+
191
+ case addr
192
+ when 0xff3e then @bus.rom_selected = true
193
+ when 0xff3f then @bus.rom_selected = false
194
+ else @ted.poke(addr, value)
195
+ end
196
+ end
197
+ end
198
+
199
+ attr_reader :ram, :ram_view, :data, :low_bank, :high_bank
200
+
201
+ # What the top page reads above $FF3F: the high ROM bank or RAM.
202
+ attr_reader :top_memory
203
+
204
+ # The CPU sees ROM at $8000-$FFFF.
205
+ attr_reader :rom_selected
206
+
207
+ # +io+ is the IOPage, +model+ a Model profile, whose KERNAL it maps
208
+ # and whose function ROMs it maps where the model has them, and
209
+ # +ram_kb+ the RAM fitted.
210
+ def initialize(ted:, port:, io:, model: Model::PLUS4, ram_kb: model.ram_kb)
211
+ addressable_at(0, length: 2**16)
212
+ size = ram_kb * 1024
213
+ @ram = Memory.new(Array.new(size, RAM_POWER_ON), length: size, start: 0)
214
+ @ram_view = MirroredRAM.new(@ram, size - 1)
215
+ @ted = ted
216
+ @open_bus = OpenBus.new(self)
217
+ @low_roms = [ROM.load("plus4/basic.rom", 0x8000), nil, nil, nil]
218
+ @high_roms = [ROM.load(model.kernal, 0xc000), nil, nil, nil]
219
+ if model.plus4_parts
220
+ @low_roms[1] = ROM.load("plus4/function-low.rom", 0x8000)
221
+ @high_roms[1] = ROM.load("plus4/function-high.rom", 0xc000)
222
+ end
223
+ @zero_page = ZeroPage.new(port, @ram_view)
224
+ @io_page = io
225
+ io.connect(self)
226
+ @top_page = TopPage.new(self, ted)
227
+ @tcbm_page = @open_bus
228
+ @debug_register = nil
229
+ @data = 0
230
+ @read_pages = Array.new(256, @open_bus)
231
+ @write_pages = Array.new(256, @ram_view)
232
+ @ted_pages = Array.new(256, @ram_view)
233
+ @ted_rom_pages = Array.new(256, @open_bus)
234
+ @low_bank = @high_bank = 0
235
+ @rom_selected = true
236
+ @top_memory = @ram_view
237
+ map_writes
238
+ map_reads
239
+ end
240
+
241
+ def power_on!
242
+ @ram.clear!(Array.new(@ram.length, RAM_POWER_ON))
243
+ reset!
244
+ end
245
+
246
+ # The RESET line selects ROM and the first bank of each.
247
+ def reset!
248
+ @low_bank = @high_bank = 0
249
+ self.rom_selected = true
250
+ end
251
+
252
+ def rom_selected=(selected)
253
+ @rom_selected = selected
254
+ @ted.rom_selected = selected
255
+ map_reads
256
+ end
257
+
258
+ # Whether the CPU reads the KERNAL at $C000-$FFFF: ROM selected, with
259
+ # the KERNAL's bank, high bank 0.
260
+ def kernal_selected? = @rom_selected && @high_bank.zero?
261
+
262
+ # A write to the ROM bank latch at +addr+.
263
+ def select_banks(addr)
264
+ @low_bank = addr & 0x03
265
+ @high_bank = (addr >> 2) & 0x03
266
+ map_reads
267
+ end
268
+
269
+ # Calls the block with each byte written to $FDCF (DebugRegister).
270
+ def install_debug_register(&)
271
+ @debug_register = DebugRegister.new(@io_page, &)
272
+ map_writes
273
+ end
274
+
275
+ # Plugs in a 1551's TCBM interface, the TIAs at $FEC0-$FEFF.
276
+ def plug_tcbm(drive8:, drive9:)
277
+ @tcbm_page = TCBMPage.new(self, drive8:, drive9:)
278
+ map_writes
279
+ map_reads
280
+ end
281
+
282
+ # Puts a cartridge's ROM chips in their slots, as a PLUS4 CARTRIDGE
283
+ # .crt numbers them: a chip's bank picks cartridge 1 (0) or 2 (1), in
284
+ # ROM banks 2 and 3, and its address the low ($8000) or the high
285
+ # ($C000) slot. A chip shorter than 16K repeats through the slot.
286
+ # Slots the chips don't name keep what they held.
287
+ def attach_cartridge(chips)
288
+ chips.each do |chip|
289
+ raise ArgumentError, "no Plus/4 cartridge slot in bank #{chip.bank}" unless chip.bank.between?(0, 1)
290
+
291
+ rom = chip.data
292
+ length = rom.length
293
+ data = Array.new(0x4000) { |i| length.zero? ? 0xff : rom[i % length] }
294
+ if chip.address < 0xc000
295
+ @low_roms[2 + chip.bank] = ROM.new(data, length: 0x4000, start: 0x8000)
296
+ else
297
+ @high_roms[2 + chip.bank] = ROM.new(data, length: 0x4000, start: 0xc000)
298
+ end
299
+ end
300
+ map_reads
301
+ end
302
+
303
+ # Empties both cartridges' slots, ROM banks 2 and 3, which then read
304
+ # as the open bus.
305
+ def detach_cartridges
306
+ @low_roms[2] = @low_roms[3] = @high_roms[2] = @high_roms[3] = nil
307
+ map_reads
308
+ end
309
+
310
+ # The slots that hold a cartridge's ROM, by xplus4's names: c1lo,
311
+ # c1hi, c2lo and c2hi.
312
+ def cartridge_slots
313
+ slots = []
314
+ slots << "c1lo" unless @low_roms[2].nil?
315
+ slots << "c1hi" unless @high_roms[2].nil?
316
+ slots << "c2lo" unless @low_roms[3].nil?
317
+ slots << "c2hi" unless @high_roms[3].nil?
318
+ slots
319
+ end
320
+
321
+ def peek(addr)
322
+ @data = @read_pages[addr >> 8].peek(addr)
323
+ end
324
+
325
+ def poke(addr, value)
326
+ @data = value
327
+ @write_pages[addr >> 8].poke(addr, value)
328
+ end
329
+
330
+ # A TED fetch of the video matrix or the attributes.
331
+ def ted_fetch(addr)
332
+ @data = @ted_pages[addr >> 8].peek(addr)
333
+ end
334
+
335
+ # A TED fetch of character or bitmap data, from ROM when +rom+.
336
+ def ted_pattern_fetch(addr, rom)
337
+ @data = rom ? @ted_rom_pages[addr >> 8].peek(addr) : @ram_view.peek(addr)
338
+ end
339
+
340
+ private
341
+
342
+ def map_writes
343
+ @write_pages[0x00] = @zero_page
344
+ @write_pages[0xfd] = @debug_register || @io_page
345
+ @write_pages[0xfe] = @tcbm_page
346
+ @write_pages[0xff] = @top_page
347
+ end
348
+
349
+ def map_reads
350
+ @read_pages.fill(@ram_view, 0x00, 0xfd)
351
+ @read_pages[0x00] = @zero_page
352
+ map_roms(@read_pages) if @rom_selected
353
+ @top_memory = @rom_selected ? @high_roms[@high_bank] || @open_bus : @ram_view
354
+ @read_pages[0xfd] = @io_page
355
+ @read_pages[0xfe] = @tcbm_page
356
+ @read_pages[0xff] = @top_page
357
+ map_ted_reads
358
+ end
359
+
360
+ def map_ted_reads
361
+ @ted_pages.fill(@ram_view, 0x80, 0x80)
362
+ map_roms(@ted_pages) if @rom_selected
363
+ map_roms(@ted_rom_pages)
364
+ @ted_pages.fill(@high_roms[@high_bank] || @open_bus, 0xfd, 3) if @rom_selected
365
+ @ted_rom_pages.fill(@high_roms[@high_bank] || @open_bus, 0xfd, 3)
366
+ end
367
+
368
+ def map_roms(pages)
369
+ pages.fill(@low_roms[@low_bank] || @open_bus, 0x80, 0x40)
370
+ pages.fill(@high_roms[@high_bank] || @open_bus, 0xc0, 0x3c)
371
+ pages[0xfc] = @high_roms[0]
372
+ end
373
+ end
374
+ end
375
+ end
@@ -0,0 +1,21 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Badline
4
+ class Plus4
5
+ # The C16's and C116's cassette sense, which has no 6529 to go through:
6
+ # a read of $FD10-$FD1F enables a 74LS125 that drives the sense line
7
+ # onto data bit 2, low while a key is down on the deck (C16 schematic
8
+ # 251788, sheet 1, U11). Nothing drives the other bits, which keep the
9
+ # last byte on the bus, and a write goes nowhere.
10
+ class CassetteSense
11
+ TAPE_SENSE = 0x04
12
+
13
+ def initialize(datasette)
14
+ @datasette = datasette
15
+ end
16
+
17
+ # The byte a read finds, from +data+, the last byte on the bus.
18
+ def read(data) = @datasette.sense_low? ? data & ~TAPE_SENSE : data | TAPE_SENSE
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,10 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Badline
4
+ class Plus4
5
+ # The 7501 or 8501, a 6510 core without an NMI pin, on the Plus4::Bus.
6
+ class CPU
7
+ include Badline::Chips::CPU::Core
8
+ end
9
+ end
10
+ end
@@ -0,0 +1,93 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Badline
4
+ class Plus4
5
+ # The 7501's I/O port at $00 (direction) and $01 (data). Its seven
6
+ # pins carry the serial bus and the cassette port, and none of them
7
+ # banks memory:
8
+ #
9
+ # - P0, P1 and P2 drive the serial bus's DATA, CLK and ATN through
10
+ # inverters, so a 1 pulls the line low, and P1 is the cassette write
11
+ # line too.
12
+ # - P3 runs the cassette motor while it is low.
13
+ # - P4 reads the cassette read line, P6 the serial CLK line and P7 the
14
+ # DATA line, 1 while the line is high.
15
+ # - P5 has no pin and reads 1.
16
+ #
17
+ # An output pin reads back what it drives. A pin left as an input reads
18
+ # what drives it from outside, and the serial outputs, P0-P2, float high
19
+ # there, so they pull their lines, as an output of 1 does.
20
+ class CPUPort
21
+ DATA_OUT = 0x01
22
+ CLK_OUT = 0x02
23
+ ATN_OUT = 0x04
24
+ MOTOR = 0x08
25
+ TAPE_READ = 0x10
26
+ CLK_IN = 0x40
27
+ DATA_IN = 0x80
28
+
29
+ attr_reader :ddr, :data
30
+
31
+ def initialize(serial_bus:, datasette:)
32
+ @serial_bus = serial_bus
33
+ @datasette = datasette
34
+ @ddr = 0
35
+ @data = 0
36
+ end
37
+
38
+ def reset!
39
+ @ddr = 0
40
+ @data = 0
41
+ changed
42
+ end
43
+
44
+ def peek(addr)
45
+ return @ddr if addr.zero?
46
+
47
+ (@data & @ddr) | (inputs & ~@ddr & 0xff)
48
+ end
49
+
50
+ def poke(addr, value)
51
+ if addr.zero?
52
+ @ddr = value
53
+ else
54
+ @data = value
55
+ end
56
+ changed
57
+ end
58
+
59
+ # The pins' levels as they leave the chip, inputs floating high.
60
+ def levels = (@data & @ddr) | (~@ddr & 0xff)
61
+
62
+ # The serial bus lines the port pulls low, as Drives::SerialBus host
63
+ # lines.
64
+ def serial_lines
65
+ out = levels
66
+ lines = 0
67
+ lines |= Drives::SerialBus::HOST_ATN_OUT if out.anybits?(ATN_OUT)
68
+ lines |= Drives::SerialBus::HOST_CLK_OUT if out.anybits?(CLK_OUT)
69
+ lines |= Drives::SerialBus::HOST_DATA_OUT if out.anybits?(DATA_OUT)
70
+ lines
71
+ end
72
+
73
+ private
74
+
75
+ def inputs
76
+ value = 0x2f
77
+ value |= TAPE_READ if @datasette.read_high?
78
+ low = @serial_bus.low_lines
79
+ value |= CLK_IN unless low.anybits?(Drives::SerialBus::CLK)
80
+ value |= DATA_IN unless low.anybits?(Drives::SerialBus::DATA)
81
+ value
82
+ end
83
+
84
+ def changed
85
+ lines = serial_lines
86
+ @serial_bus.host_lines = lines unless lines == @serial_bus.host_lines
87
+ out = levels
88
+ @datasette.motor = out.nobits?(MOTOR)
89
+ @datasette.write_line = out.anybits?(CLK_OUT)
90
+ end
91
+ end
92
+ end
93
+ end