badline 0.13.0 → 0.14.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (254) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +49 -1
  3. data/CLAUDE.md +27 -7
  4. data/CONTRIBUTING.md +4 -3
  5. data/README.md +8 -5
  6. data/doc/media.md +2 -1
  7. data/doc/pinned-behaviour.md +29 -0
  8. data/doc/snapshot-format.md +1047 -0
  9. data/doc/snapshots.md +72 -39
  10. data/lib/badline/audio/bare_player.rb +50 -17
  11. data/lib/badline/audio/machine_player.rb +15 -9
  12. data/lib/badline/audio/renderer.rb +32 -21
  13. data/lib/badline/c128/banks.rb +6 -3
  14. data/lib/badline/c128/bus/keyed_state.rb +45 -0
  15. data/lib/badline/c128/bus.rb +17 -3
  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 +20 -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/keyed_state.rb +41 -0
  31. data/lib/badline/c64/bus.rb +2 -1
  32. data/lib/badline/c64/cartridge/action_replay.rb +0 -13
  33. data/lib/badline/c64/cartridge/easy_flash.rb +0 -15
  34. data/lib/badline/c64/cartridge/epyx_fastload.rb +0 -8
  35. data/lib/badline/c64/cartridge/final_cartridge3.rb +0 -12
  36. data/lib/badline/c64/cartridge/flash.rb +3 -3
  37. data/lib/badline/c64/cartridge/g_mod2.rb +0 -12
  38. data/lib/badline/c64/cartridge/geo_ram.rb +0 -16
  39. data/lib/badline/c64/cartridge/kcs_power.rb +0 -8
  40. data/lib/badline/c64/cartridge/keyed_state.rb +393 -0
  41. data/lib/badline/c64/cartridge/pagefox.rb +0 -10
  42. data/lib/badline/c64/cartridge/retro_replay.rb +0 -8
  43. data/lib/badline/c64/cartridge/rgcd.rb +0 -10
  44. data/lib/badline/c64/cartridge/super_games.rb +0 -10
  45. data/lib/badline/c64/cartridge/zaxxon.rb +0 -16
  46. data/lib/badline/c64/cartridge.rb +1 -27
  47. data/lib/badline/c64/kernal_traps.rb +28 -25
  48. data/lib/badline/c64/keyed_modules.rb +111 -0
  49. data/lib/badline/c64/keyed_state.rb +68 -0
  50. data/lib/badline/c64/reu/dma.rb +0 -4
  51. data/lib/badline/c64/reu/keyed_state.rb +140 -0
  52. data/lib/badline/c64/reu/ram.rb +0 -15
  53. data/lib/badline/c64/reu.rb +1 -3
  54. data/lib/badline/c64/true_drives.rb +56 -5
  55. data/lib/badline/c64.rb +7 -3
  56. data/lib/badline/chips/cia/interrupt_register.rb +0 -17
  57. data/lib/badline/chips/cia/keyed_parts.rb +213 -0
  58. data/lib/badline/chips/cia/serial.rb +0 -4
  59. data/lib/badline/chips/cia/time_of_day.rb +0 -22
  60. data/lib/badline/chips/cia/timer.rb +0 -20
  61. data/lib/badline/chips/cia.rb +2 -2
  62. data/lib/badline/chips/cpu/core.rb +18 -2
  63. data/lib/badline/chips/cpu/keyed_state.rb +177 -0
  64. data/lib/badline/chips/cpu/microcode.rb +16 -0
  65. data/lib/badline/chips/cpu/port.rb +20 -3
  66. data/lib/badline/chips/cpu.rb +5 -0
  67. data/lib/badline/chips/sid/decimator.rb +10 -8
  68. data/lib/badline/chips/sid/envelope.rb +1 -6
  69. data/lib/badline/chips/sid/filter.rb +0 -14
  70. data/lib/badline/chips/sid/keyed_parts.rb +286 -0
  71. data/lib/badline/chips/sid/voice.rb +0 -10
  72. data/lib/badline/chips/sid/waveform.rb +0 -2
  73. data/lib/badline/chips/sid.rb +1 -2
  74. data/lib/badline/chips/ted/beam.rb +119 -0
  75. data/lib/badline/chips/ted/timers.rb +66 -0
  76. data/lib/badline/chips/ted.rb +143 -0
  77. data/lib/badline/chips/tia.rb +49 -0
  78. data/lib/badline/chips/vdc/keyed_parts.rb +105 -0
  79. data/lib/badline/chips/vdc/painter.rb +0 -12
  80. data/lib/badline/chips/vdc/raster.rb +0 -16
  81. data/lib/badline/chips/vdc/window.rb +0 -9
  82. data/lib/badline/chips/vdc.rb +1 -2
  83. data/lib/badline/chips/via/control_lines.rb +0 -15
  84. data/lib/badline/chips/via/interrupt_register.rb +0 -9
  85. data/lib/badline/chips/via/keyed_parts.rb +163 -0
  86. data/lib/badline/chips/via/shift_register.rb +0 -14
  87. data/lib/badline/chips/via/timer1.rb +0 -13
  88. data/lib/badline/chips/via/timer2.rb +0 -18
  89. data/lib/badline/chips/via.rb +1 -2
  90. data/lib/badline/chips/vic/border_mask.rb +0 -10
  91. data/lib/badline/chips/vic/collisions.rb +0 -11
  92. data/lib/badline/chips/vic/color_patches.rb +0 -11
  93. data/lib/badline/chips/vic/display_state.rb +0 -19
  94. data/lib/badline/chips/vic/graphics_shifter.rb +0 -4
  95. data/lib/badline/chips/vic/keyed_parts.rb +228 -0
  96. data/lib/badline/chips/vic/keyed_sprites.rb +225 -0
  97. data/lib/badline/chips/vic/keyed_state.rb +102 -0
  98. data/lib/badline/chips/vic/register_log.rb +0 -18
  99. data/lib/badline/chips/vic/registers.rb +0 -9
  100. data/lib/badline/chips/vic/sequencer.rb +0 -2
  101. data/lib/badline/chips/vic/sprite/internal_bus.rb +0 -11
  102. data/lib/badline/chips/vic/sprite.rb +0 -2
  103. data/lib/badline/chips/vic/sprites.rb +0 -3
  104. data/lib/badline/chips/vic.rb +6 -2
  105. data/lib/badline/chips/vic_i/keyed_parts.rb +142 -0
  106. data/lib/badline/chips/vic_i/painter.rb +0 -4
  107. data/lib/badline/chips/vic_i/sound.rb +38 -46
  108. data/lib/badline/chips/vic_i.rb +2 -2
  109. data/lib/badline/chips/z80/core.rb +2 -2
  110. data/lib/badline/chips/z80/keyed_state.rb +73 -0
  111. data/lib/badline/chips.rb +10 -0
  112. data/lib/badline/core.rb +4 -21
  113. data/lib/badline/drives/datasette/keyed_state.rb +38 -0
  114. data/lib/badline/drives/datasette.rb +40 -7
  115. data/lib/badline/drives/engine/core.rb +3 -0
  116. data/lib/badline/drives/engine/serial_lines.rb +4 -7
  117. data/lib/badline/drives/model1541/bus.rb +0 -13
  118. data/lib/badline/drives/model1541/disk.rb +9 -8
  119. data/lib/badline/drives/model1541/keyed_state.rb +251 -0
  120. data/lib/badline/drives/model1541/mechanism.rb +0 -2
  121. data/lib/badline/drives/model1541.rb +1 -2
  122. data/lib/badline/drives/model1571/bus.rb +0 -13
  123. data/lib/badline/drives/model1571/keyed_state.rb +108 -0
  124. data/lib/badline/drives/model1571/wd1770.rb +0 -14
  125. data/lib/badline/drives/model1571.rb +1 -2
  126. data/lib/badline/drives/model1581/bus.rb +0 -13
  127. data/lib/badline/drives/model1581/disk.rb +0 -4
  128. data/lib/badline/drives/model1581/keyed_state.rb +280 -0
  129. data/lib/badline/drives/model1581/mechanism.rb +0 -21
  130. data/lib/badline/drives/model1581/slot.rb +0 -18
  131. data/lib/badline/drives/model1581/wd1772.rb +0 -2
  132. data/lib/badline/drives/model1581.rb +2 -2
  133. data/lib/badline/drives.rb +8 -0
  134. data/lib/badline/frontend/app.rb +3 -3
  135. data/lib/badline/frontend/boot.rb +1 -1
  136. data/lib/badline/frontend/controls.rb +19 -3
  137. data/lib/badline/frontend/drive_led.rb +44 -21
  138. data/lib/badline/frontend/menu/menu_dialogs.rb +2 -2
  139. data/lib/badline/frontend/snapshots.rb +12 -1
  140. data/lib/badline/frontend/timeline.rb +28 -10
  141. data/lib/badline/input/matrix_typist.rb +98 -0
  142. data/lib/badline/input.rb +1 -0
  143. data/lib/badline/kernal_trap/chrout.rb +45 -0
  144. data/lib/badline/kernal_trap/dos/channel.rb +0 -27
  145. data/lib/badline/kernal_trap/dos/channels.rb +0 -25
  146. data/lib/badline/kernal_trap/dos/memory.rb +0 -9
  147. data/lib/badline/kernal_trap/dos/status.rb +0 -8
  148. data/lib/badline/kernal_trap/dos/write_file.rb +0 -18
  149. data/lib/badline/kernal_trap/dos.rb +0 -19
  150. data/lib/badline/kernal_trap/file_routine.rb +4 -4
  151. data/lib/badline/kernal_trap/keyed_state.rb +150 -0
  152. data/lib/badline/kernal_trap/layout.rb +75 -11
  153. data/lib/badline/kernal_trap/load.rb +14 -16
  154. data/lib/badline/kernal_trap/routine.rb +12 -0
  155. data/lib/badline/kernal_trap/save.rb +8 -18
  156. data/lib/badline/kernal_trap/serial.rb +1 -17
  157. data/lib/badline/kernal_trap.rb +2 -0
  158. data/lib/badline/machine/keyboard_buffer.rb +49 -0
  159. data/lib/badline/machine/settling.rb +29 -0
  160. data/lib/badline/machine.rb +5 -2
  161. data/lib/badline/media/boot_disk.rb +14 -2
  162. data/lib/badline/media/extensions.rb +10 -0
  163. data/lib/badline/memory.rb +3 -6
  164. data/lib/badline/options/family.rb +5 -2
  165. data/lib/badline/options/help.rb +42 -16
  166. data/lib/badline/options/table.rb +2 -0
  167. data/lib/badline/options/validation.rb +2 -0
  168. data/lib/badline/options.rb +6 -1
  169. data/lib/badline/plus4/acia.rb +45 -0
  170. data/lib/badline/plus4/bus.rb +312 -0
  171. data/lib/badline/plus4/cpu.rb +10 -0
  172. data/lib/badline/plus4/cpu_port.rb +93 -0
  173. data/lib/badline/plus4/keyboard_port.rb +51 -0
  174. data/lib/badline/plus4/model.rb +38 -0
  175. data/lib/badline/plus4/user_port.rb +31 -0
  176. data/lib/badline/plus4.rb +164 -0
  177. data/lib/badline/roms/README +38 -0
  178. data/lib/badline/roms/plus4/basic.rom +0 -0
  179. data/lib/badline/roms/plus4/function-high.rom +0 -0
  180. data/lib/badline/roms/plus4/function-low.rom +0 -0
  181. data/lib/badline/roms/plus4/kernal-ntsc.rom +0 -0
  182. data/lib/badline/roms/plus4/kernal-pal.rom +0 -0
  183. data/lib/badline/snapshot/c128_setup.rb +9 -20
  184. data/lib/badline/snapshot/c64_setup.rb +28 -24
  185. data/lib/badline/snapshot/container.rb +4 -0
  186. data/lib/badline/snapshot/field_doc/markdown.rb +112 -0
  187. data/lib/badline/snapshot/field_doc.rb +109 -0
  188. data/lib/badline/snapshot/image.rb +134 -28
  189. data/lib/badline/snapshot/keyed/cursor.rb +40 -0
  190. data/lib/badline/snapshot/keyed/modules.rb +66 -0
  191. data/lib/badline/snapshot/keyed/reader.rb +188 -0
  192. data/lib/badline/snapshot/keyed/recorder.rb +88 -0
  193. data/lib/badline/snapshot/keyed/report.rb +26 -0
  194. data/lib/badline/snapshot/keyed/writer.rb +55 -0
  195. data/lib/badline/snapshot/keyed.rb +64 -0
  196. data/lib/badline/snapshot/machine_state.rb +17 -64
  197. data/lib/badline/snapshot/storage_setup.rb +29 -34
  198. data/lib/badline/snapshot/vic20_setup.rb +6 -15
  199. data/lib/badline/snapshot/vice/reu1764.rb +2 -2
  200. data/lib/badline/snapshot/vice.rb +7 -31
  201. data/lib/badline/snapshot.rb +43 -20
  202. data/lib/badline/storage/crt_file.rb +3 -1
  203. data/lib/badline/storage/g64_image.rb +8 -3
  204. data/lib/badline/storage/tap.rb +43 -15
  205. data/lib/badline/version.rb +1 -1
  206. data/lib/badline/vic20/attachments.rb +3 -0
  207. data/lib/badline/vic20/bus/keyed_state.rb +52 -0
  208. data/lib/badline/vic20/bus.rb +1 -2
  209. data/lib/badline/vic20/keyed_modules.rb +73 -0
  210. data/lib/badline/vic20/keyed_state.rb +45 -0
  211. data/lib/badline/vic20.rb +5 -5
  212. data/lib/badline.rb +1 -0
  213. metadata +61 -42
  214. data/lib/badline/c128/bus/saved_state.rb +0 -53
  215. data/lib/badline/c128/saved_state.rb +0 -117
  216. data/lib/badline/c64/bus/saved_state.rb +0 -68
  217. data/lib/badline/c64/cartridge/flash/saved_state.rb +0 -40
  218. data/lib/badline/c64/cartridge/retro_replay/saved_state.rb +0 -47
  219. data/lib/badline/c64/cartridge/saved_state.rb +0 -63
  220. data/lib/badline/c64/reu/dma/saved_state.rb +0 -52
  221. data/lib/badline/c64/reu/saved_state.rb +0 -55
  222. data/lib/badline/c64/saved_state.rb +0 -112
  223. data/lib/badline/chips/cia/saved_state.rb +0 -49
  224. data/lib/badline/chips/cia/serial/saved_state.rb +0 -45
  225. data/lib/badline/chips/cpu/saved_state.rb +0 -117
  226. data/lib/badline/chips/sid/envelope/saved_state.rb +0 -70
  227. data/lib/badline/chips/sid/filter/saved_state.rb +0 -42
  228. data/lib/badline/chips/sid/saved_state.rb +0 -52
  229. data/lib/badline/chips/sid/waveform/saved_state.rb +0 -68
  230. data/lib/badline/chips/vdc/saved_state.rb +0 -36
  231. data/lib/badline/chips/via/saved_state.rb +0 -44
  232. data/lib/badline/chips/vic/graphics_shifter/saved_state.rb +0 -40
  233. data/lib/badline/chips/vic/saved_state.rb +0 -82
  234. data/lib/badline/chips/vic/sequencer/saved_state.rb +0 -91
  235. data/lib/badline/chips/vic/sprite/saved_state.rb +0 -66
  236. data/lib/badline/chips/vic/sprites/saved_state.rb +0 -59
  237. data/lib/badline/chips/vic_i/painter/saved_state.rb +0 -40
  238. data/lib/badline/chips/vic_i/saved_state.rb +0 -55
  239. data/lib/badline/chips/z80/saved_state.rb +0 -71
  240. data/lib/badline/chrout_trap.rb +0 -43
  241. data/lib/badline/debug_register.rb +0 -28
  242. data/lib/badline/drives/datasette/saved_state.rb +0 -37
  243. data/lib/badline/drives/model1541/disk/saved_state.rb +0 -88
  244. data/lib/badline/drives/model1541/mechanism/saved_state.rb +0 -72
  245. data/lib/badline/drives/model1541/saved_state.rb +0 -44
  246. data/lib/badline/drives/model1571/saved_state.rb +0 -47
  247. data/lib/badline/drives/model1581/disk/saved_state.rb +0 -89
  248. data/lib/badline/drives/model1581/saved_state.rb +0 -42
  249. data/lib/badline/drives/model1581/wd1772/saved_state.rb +0 -68
  250. data/lib/badline/keyboard_buffer.rb +0 -33
  251. data/lib/badline/snapshot/setup.rb +0 -31
  252. data/lib/badline/snapshot/state.rb +0 -179
  253. data/lib/badline/vic20/bus/saved_state.rb +0 -35
  254. data/lib/badline/vic20/saved_state.rb +0 -110
@@ -20,7 +20,11 @@ module Badline
20
20
  # +banked+ says whether the 6510 port can bank the KERNAL out, and so
21
21
  # whether RAM lies under every ROM and the I/O. +mmu+ says the C128's
22
22
  # MMU maps the KERNAL instead, and that the file name and the file's
23
- # bytes lie in the banks the KERNAL keeps at FNBANK and BA. Each
23
+ # bytes lie in the banks the KERNAL keeps at FNBANK and BA.
24
+ # +rom_select+ says the 264 series' TED selects ROM or RAM and its
25
+ # latch the ROM banks, so the KERNAL is in while ROM is selected with
26
+ # the KERNAL's bank, and the ROM reads the file name and the file's
27
+ # bytes from RAM, under any ROM, through its bank fetch at $07D9. Each
24
28
  # serial byte starts a one-shot timer by writing the high byte of its
25
29
  # count to +serial_timer+, and the control value TIMER_ONE_SHOT_START
26
30
  # to +serial_timer_start+ where the timer has a control register to
@@ -29,16 +33,19 @@ module Badline
29
33
  # +serial_timer+. The ROM lets go of each serial line by reading a
30
34
  # port, masking the line's bit off and writing it back: ATN at
31
35
  # +atn_port+ with +atn_mask+, then the clock and the data line.
36
+ # +zero_page+ is a ZeroPage, where the KERNAL keeps the file's
37
+ # parameters and the status the traps read and set.
32
38
  Layout = Data.define(:load, :save, :searching_message, :loading_message, :load_byte_loop, :load_done,
33
39
  :file_not_found_exit, :missing_file_name_exit, :saving_message, :clock_release,
34
40
  :data_release, :save_done, :talk, :listen, :second, :tksa, :ciout, :untalk,
35
41
  :unlisten, :acptr, :banked, :serial_timer, :serial_timer_start, :atn_port, :atn_mask,
36
- :clock_port, :clock_mask, :data_port, :data_mask, :mmu) do
42
+ :clock_port, :clock_mask, :data_port, :data_mask, :mmu, :rom_select, :zero_page) do
37
43
  # Whether the CPU reads the KERNAL ROM at the trapped addresses: on a
38
- # banked machine, the 6510 port's banking bits select it, and on the
39
- # C128 the MMU.
44
+ # banked machine, the 6510 port's banking bits select it, on the
45
+ # C128 the MMU, and on the 264 series the ROM select and bank latch.
40
46
  def kernal_mapped?(bus)
41
47
  return bus.system_rom_mapped? if mmu
48
+ return bus.kernal_selected? if rom_select
42
49
 
43
50
  !banked || bus.io_port.kernal?
44
51
  end
@@ -55,15 +62,21 @@ module Badline
55
62
  end
56
63
 
57
64
  # The file name's byte at +address+, in the bank at FNBANK on the
58
- # C128.
65
+ # C128 and in RAM on the 264 series.
59
66
  def filename_byte(bus, address)
60
- mmu ? bus.peek_bank(bus.peek(FILENAME_BANK), address) : bus.peek(address)
67
+ return bus.peek_bank(bus.peek(FILENAME_BANK), address) if mmu
68
+ return bus.ram_view.peek(address) if rom_select
69
+
70
+ bus.peek(address)
61
71
  end
62
72
 
63
73
  # The byte at +address+ a SAVE writes or a VERIFY compares, in the bank
64
- # at BA on the C128.
74
+ # at BA on the C128 and in RAM on the 264 series.
65
75
  def file_byte(bus, address)
66
- mmu ? bus.peek_bank(bus.peek(BANK), address) : bus.peek(address)
76
+ return bus.peek_bank(bus.peek(BANK), address) if mmu
77
+ return bus.ram_view.peek(address) if rom_select
78
+
79
+ bus.peek(address)
67
80
  end
68
81
 
69
82
  # Starts the timer the ROM times a serial byte with, its count's high
@@ -95,6 +108,32 @@ module Badline
95
108
  end
96
109
  end
97
110
 
111
+ # The zero-page variables of a KERNAL's file routines, by address:
112
+ # +status+ (ST), +verify+ (VERCK, 0 for LOAD and 1 for VERIFY),
113
+ # +start_address+ (SAL), +end_address+ (EAL), +filename_length+
114
+ # (FNLEN), +secondary_address+ (SA), +device+ (FA), +filename_address+
115
+ # (FNADR), +save_start+ (STAL) and +load_address+ (MEMUSS, where LOAD
116
+ # puts a file loaded with secondary address 0). Each two-byte one names
117
+ # its low byte, and the high byte follows it.
118
+ ZeroPage = Data.define(:status, :verify, :start_address, :end_address, :filename_length, :secondary_address,
119
+ :device, :filename_address, :save_start, :load_address)
120
+
121
+ # The C64's file variables. The VIC-20 and C128 KERNALs keep theirs at
122
+ # the same addresses.
123
+ C64_ZERO_PAGE = ZeroPage.new(
124
+ status: 0x90, verify: 0x93, start_address: 0xac, end_address: 0xae, filename_length: 0xb7,
125
+ secondary_address: 0xb9, device: 0xba, filename_address: 0xbb, save_start: 0xc1, load_address: 0xc3
126
+ )
127
+
128
+ # The 264 series KERNAL's file variables, as its LOAD ($F04A) and SAVE
129
+ # ($F1A4) use them: SAVE walks SAL at $9B from STAL at $B2 up to EAL at
130
+ # $9D, and LOAD ends with EAL at $9D, or starts it from MEMUSS at $B4
131
+ # for secondary address 0.
132
+ PLUS4_ZERO_PAGE = ZeroPage.new(
133
+ status: 0x90, verify: 0x93, start_address: 0x9b, end_address: 0x9d, filename_length: 0xab,
134
+ secondary_address: 0xad, device: 0xae, filename_address: 0xaf, save_start: 0xb2, load_address: 0xb4
135
+ )
136
+
98
137
  # A CIA's control value that starts its timer: force load, one-shot,
99
138
  # start.
100
139
  TIMER_ONE_SHOT_START = 0x19
@@ -117,7 +156,7 @@ module Badline
117
156
  ciout: 0xeddd, untalk: 0xedef, unlisten: 0xedfe, acptr: 0xee13,
118
157
  banked: true, serial_timer: 0xdc07, serial_timer_start: 0xdc0f,
119
158
  atn_port: 0xdd00, atn_mask: 0xf7, clock_port: 0xdd00, clock_mask: 0xef, data_port: 0xdd00, data_mask: 0xdf,
120
- mmu: false
159
+ mmu: false, rom_select: false, zero_page: C64_ZERO_PAGE
121
160
  )
122
161
 
123
162
  # The VIC-20 KERNAL, 901486-07 (PAL). The NTSC one, 901486-06, differs
@@ -135,7 +174,7 @@ module Badline
135
174
  ciout: 0xeee4, untalk: 0xeef6, unlisten: 0xef04, acptr: 0xef19,
136
175
  banked: false, serial_timer: 0x9129, serial_timer_start: nil,
137
176
  atn_port: 0x911f, atn_mask: 0x7f, clock_port: 0x912c, clock_mask: 0xfd, data_port: 0x912c, data_mask: 0xdf,
138
- mmu: false
177
+ mmu: false, rom_select: false, zero_page: C64_ZERO_PAGE
139
178
  )
140
179
 
141
180
  # The C128 KERNAL, 318020-05, in C128 mode: the jump table's targets
@@ -156,7 +195,32 @@ module Badline
156
195
  ciout: 0xe503, untalk: 0xe515, unlisten: 0xe526, acptr: 0xe43e,
157
196
  banked: true, serial_timer: nil, serial_timer_start: nil,
158
197
  atn_port: 0xdd00, atn_mask: 0xf7, clock_port: 0xdd00, clock_mask: 0xef, data_port: 0xdd00, data_mask: 0xdf,
159
- mmu: true
198
+ mmu: true, rom_select: false, zero_page: C64_ZERO_PAGE
199
+ )
200
+
201
+ # The 264 series KERNAL, 318004-05 (PAL), the same at every address
202
+ # here in the NTSC 318005-05: ILOAD's and ISAVE's default targets and
203
+ # the jump table's targets for the serial primitives, each of which
204
+ # tries a 1551's TCBM interface for devices 8 and 9 before the serial
205
+ # bus. LOAD prints SEARCHING FOR at $F160 and LOADING or VERIFYING at
206
+ # $F189, which ends CLC, retries a byte from $F09F and returns at $F0EB
207
+ # (LDX $9D, LDY $9E, RTS). FILE NOT FOUND leaves through $F27C and
208
+ # MISSING FILE NAME through $F288. SAVE prints SAVING at $F228 and
209
+ # returns at $F226 (CLC, RTS). The port at $01 drives the lines, ATN
210
+ # from bit 2, the clock from bit 1 ($E2B8 lets it go) and the data line
211
+ # from bit 0 ($E2C6). Its serial bytes are timed with TED timer 2, but
212
+ # its timeouts differ from the C64's, so the traps leave the timer
213
+ # alone.
214
+ PLUS4_LAYOUT = Layout.new(
215
+ load: 0xf04a, save: 0xf1a4,
216
+ searching_message: 0xf160, loading_message: 0xf189, load_byte_loop: 0xf09f, load_done: 0xf0eb,
217
+ file_not_found_exit: 0xf27c, missing_file_name_exit: 0xf288,
218
+ saving_message: 0xf228, clock_release: 0xe2b8, data_release: 0xe2c6, save_done: 0xf226,
219
+ talk: 0xedfa, listen: 0xee2c, second: 0xee4d, tksa: 0xee1a,
220
+ ciout: 0xecdf, untalk: 0xef3b, unlisten: 0xef23, acptr: 0xec8b,
221
+ banked: false, serial_timer: nil, serial_timer_start: nil,
222
+ atn_port: 0x01, atn_mask: 0xfb, clock_port: 0x01, clock_mask: 0xfd, data_port: 0x01, data_mask: 0xfe,
223
+ mmu: false, rom_select: true, zero_page: PLUS4_ZERO_PAGE
160
224
  )
161
225
  end
162
226
  end
@@ -31,8 +31,8 @@ module Badline
31
31
  def call
32
32
  return unless active?
33
33
 
34
- @bus.poke(0x93, @cpu.a)
35
- @bus.poke(0x90, 0x00)
34
+ @bus.poke(zero_page.verify, @cpu.a)
35
+ @bus.poke(zero_page.status, 0x00)
36
36
  name = filename
37
37
  return @cpu.program_counter = @layout.missing_file_name_exit if Storage.parse_name(name).first.empty?
38
38
 
@@ -66,17 +66,17 @@ module Badline
66
66
  # until RUN/STOP breaks the load, as on a real drive.
67
67
  def finish(data, complete)
68
68
  if data.empty?
69
- @bus.poke(0x90, EOI | READ_TIMEOUT)
69
+ @bus.poke(zero_page.status, EOI | READ_TIMEOUT)
70
70
  leave_bus(ACPTR_TIMEOUT)
71
71
  continue_with(@layout.searching_message, @layout.file_not_found_exit)
72
72
  else
73
73
  deliver(data)
74
- @bus.poke(0x90, @bus.peek(0x90) & ~EOI) unless complete
74
+ @bus.poke(zero_page.status, @bus.peek(zero_page.status) & ~EOI) unless complete
75
75
  leave_bus(ISOUR_TIMEOUT)
76
76
  continue_with(@layout.searching_message, @layout.loading_message,
77
77
  complete ? @layout.load_done : @layout.load_byte_loop)
78
78
  end
79
- @bus.poke(0xb9, LOAD_SECONDARY)
79
+ @bus.poke(zero_page.secondary_address, LOAD_SECONDARY)
80
80
  end
81
81
 
82
82
  # A load ends with the UNLISTEN that closes the file, a byte sent,
@@ -91,9 +91,9 @@ module Badline
91
91
  load? && data.length > 2 && load_address(data) < 0x0334
92
92
  end
93
93
 
94
- # A=0 is LOAD, A=1 is VERIFY, kept at $93 (VERCK)
94
+ # A=0 is LOAD, A=1 is VERIFY, kept at VERCK
95
95
  def load?
96
- @bus.peek(0x93).zero?
96
+ @bus.peek(zero_page.verify).zero?
97
97
  end
98
98
 
99
99
  def deliver(data)
@@ -102,25 +102,23 @@ module Badline
102
102
  payload = payload[0, 0x10000 - addr] if addr + payload.length > 0x10000
103
103
  if load?
104
104
  @layout.store_file(@bus, addr, payload)
105
- @bus.poke(0x90, EOI)
105
+ @bus.poke(zero_page.status, EOI)
106
106
  else
107
107
  verify(addr, payload)
108
108
  end
109
- end_addr = (addr + payload.length) & 0xffff
110
- @bus.poke(0xae, low_byte(end_addr))
111
- @bus.poke(0xaf, high_byte(end_addr))
109
+ poke_word(zero_page.end_address, (addr + payload.length) & 0xffff)
112
110
  end
113
111
 
114
112
  def verify(addr, payload)
115
113
  match = payload.each_with_index.all? { |byte, i| @layout.file_byte(@bus, addr + i) == byte }
116
- @bus.poke(0x90, match ? EOI : EOI | VERIFY_MISMATCH)
114
+ @bus.poke(zero_page.status, match ? EOI : EOI | VERIFY_MISMATCH)
117
115
  end
118
116
 
119
- # Secondary address $B9 zero relocates to the caller-supplied address
120
- # stashed at $C3/$C4 (MEMUSS)
117
+ # Secondary address zero relocates to the caller-supplied address
118
+ # stashed at MEMUSS
121
119
  def load_address(data)
122
- if @bus.peek(0xb9).zero?
123
- uint16(@bus.peek(0xc3), @bus.peek(0xc4))
120
+ if @bus.peek(zero_page.secondary_address).zero?
121
+ peek_word(zero_page.load_address)
124
122
  else
125
123
  uint16(data[0], data[1].to_i)
126
124
  end
@@ -26,6 +26,18 @@ module Badline
26
26
  @layout.kernal_mapped?(@bus)
27
27
  end
28
28
 
29
+ def zero_page = @layout.zero_page
30
+
31
+ # The two-byte value whose low byte is at +address+.
32
+ def peek_word(address)
33
+ uint16(@bus.peek(address), @bus.peek(address + 1))
34
+ end
35
+
36
+ def poke_word(address, value)
37
+ @bus.poke(address, low_byte(value))
38
+ @bus.poke(address + 1, high_byte(value))
39
+ end
40
+
29
41
  # The ROM times each byte on the serial bus with a one-shot timer
30
42
  # (Layout#time_serial_byte): ISOUR_TIMEOUT for a byte sent,
31
43
  # ACPTR_TIMEOUT for one received.
@@ -18,7 +18,7 @@ module Badline
18
18
  class Save < FileRoutine
19
19
  SECONDARY = 0x61
20
20
 
21
- # ST bit at $90
21
+ # ST bit
22
22
  DEVICE_NOT_PRESENT = 0x80
23
23
 
24
24
  def initialize(cpu:, bus:, layout:, dos:)
@@ -27,23 +27,15 @@ module Badline
27
27
  @saving = false
28
28
  end
29
29
 
30
- def save_state(out)
31
- out.boolean(@saving)
32
- end
33
-
34
- def load_state(input)
35
- @saving = input.boolean?
36
- end
37
-
38
30
  def call
39
31
  return unless active?
40
32
  return finish if @saving
41
33
  return unless @dos.saves?
42
34
 
43
- @bus.poke(0xb9, SECONDARY)
35
+ @bus.poke(zero_page.secondary_address, SECONDARY)
44
36
  return @cpu.program_counter = @layout.missing_file_name_exit if name.empty?
45
37
 
46
- @bus.poke(0x90, 0x00)
38
+ @bus.poke(zero_page.status, 0x00)
47
39
  @saving = true
48
40
  continue_with(@layout.saving_message, @layout.save)
49
41
  end
@@ -57,19 +49,17 @@ module Badline
57
49
  def finish
58
50
  @saving = false
59
51
  saved = @dos.save(name, payload, replace: full_filename.start_with?("@"))
60
- @bus.poke(0x90, DEVICE_NOT_PRESENT) unless saved
61
- @bus.poke(0xac, @bus.peek(0xae))
62
- @bus.poke(0xad, @bus.peek(0xaf))
52
+ @bus.poke(zero_page.status, DEVICE_NOT_PRESENT) unless saved
53
+ poke_word(zero_page.start_address, peek_word(zero_page.end_address))
63
54
  @cpu.y = 0
64
55
  @cpu.status.overflow = true
65
56
  continue_with(@layout.clock_release, @layout.data_release, @layout.save_done)
66
57
  end
67
58
 
68
- # Start address at $C1/$C2 (STAL), end address (exclusive) at
69
- # $AE/$AF (EAL)
59
+ # Start address at STAL, end address (exclusive) at EAL
70
60
  def payload
71
- start = uint16(@bus.peek(0xc1), @bus.peek(0xc2))
72
- length = (uint16(@bus.peek(0xae), @bus.peek(0xaf)) - start) & 0xffff
61
+ start = peek_word(zero_page.save_start)
62
+ length = (peek_word(zero_page.end_address) - start) & 0xffff
73
63
  [low_byte(start), high_byte(start)] +
74
64
  Array.new(length) { |i| @layout.file_byte(@bus, (start + i) & 0xffff) }
75
65
  end
@@ -35,22 +35,6 @@ module Badline
35
35
  @buffer = []
36
36
  end
37
37
 
38
- # The frame under way, with the channels and the bytes it gathered.
39
- # The device it answers is the machine's to set.
40
- def save_state(out)
41
- out.boolean(@listening).boolean(@talking).optional_int(@listen_channel).optional_int(@talk_channel)
42
- out.int(@frame).blob(@buffer)
43
- end
44
-
45
- def load_state(input)
46
- @listening = input.boolean?
47
- @talking = input.boolean?
48
- @listen_channel = input.optional_int
49
- @talk_channel = input.optional_int
50
- @frame = input.int
51
- @buffer = input.blob
52
- end
53
-
54
38
  def install
55
39
  routines.each do |address, routine|
56
40
  @cpu.install_trap(address) { call_routine(routine) if kernal_mapped? }
@@ -205,7 +189,7 @@ module Badline
205
189
  def update_status(bits)
206
190
  return if bits.zero?
207
191
 
208
- @bus.poke(0x90, @bus.peek(0x90) | bits)
192
+ @bus.poke(zero_page.status, @bus.peek(zero_page.status) | bits)
209
193
  end
210
194
  end
211
195
  end
@@ -7,3 +7,5 @@ require "badline/kernal_trap/file_routine"
7
7
  require "badline/kernal_trap/load"
8
8
  require "badline/kernal_trap/save"
9
9
  require "badline/kernal_trap/serial"
10
+ require "badline/kernal_trap/chrout"
11
+ require "badline/kernal_trap/keyed_state"
@@ -0,0 +1,49 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Badline
4
+ module Machine
5
+ # Typing into the KERNAL's keyboard buffer once the machine has booted.
6
+ # The machine that includes it answers #keyboard_buffer, the Layout of
7
+ # the KERNAL it runs, and #ram.
8
+ module KeyboardBuffer
9
+ # Where a KERNAL keeps its keyboard buffer (KEYD), the count of
10
+ # characters in it (NDX), and how many it holds.
11
+ Layout = Data.define(:address, :count, :capacity)
12
+
13
+ # The C64's and the VIC-20's.
14
+ C64_LAYOUT = Layout.new(address: 0x0277, count: 0xc6, capacity: 10)
15
+
16
+ # BASIC 7.0's, in C128 mode.
17
+ C128_LAYOUT = Layout.new(address: 0x034a, count: 0xd0, capacity: 10)
18
+
19
+ # The 264 series KERNAL's, which sets its capacity at $053F to 10
20
+ # ($D86E).
21
+ PLUS4_LAYOUT = Layout.new(address: 0x0527, count: 0xef, capacity: 10)
22
+
23
+ def type_text(text)
24
+ codes = text.bytes.map { |b| ascii_to_petscii(b) }
25
+ on_init { @pending_keys = codes }
26
+ end
27
+
28
+ private
29
+
30
+ def feed_keyboard
31
+ buffer = keyboard_buffer
32
+ return unless ram.peek(buffer.count).zero?
33
+
34
+ chunk = @pending_keys.shift(buffer.capacity)
35
+ ram.write(buffer.address, chunk)
36
+ ram.poke(buffer.count, chunk.length)
37
+ @pending_keys = nil if @pending_keys.empty?
38
+ end
39
+
40
+ def ascii_to_petscii(byte)
41
+ case byte
42
+ when 0x61..0x7a then byte - 0x20
43
+ when 0x41..0x5a then byte + 0x80
44
+ else byte
45
+ end
46
+ end
47
+ end
48
+ end
49
+ end
@@ -0,0 +1,29 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Badline
4
+ module Machine
5
+ # Brings the machine to the point a snapshot file is saved at: the main
6
+ # CPU between instructions, or jammed, with no REU transfer holding the
7
+ # bus. C64, Vic20 and C128 each include it, and answer
8
+ # #settled_for_snapshot? and #settle_chips_for_snapshot, which brings
9
+ # what each chip defers up to the cycle without running one.
10
+ module Settling
11
+ # The cycles settling may run: the longest instruction stretched by
12
+ # the VIC's longest hold on the bus, and an REU swap of 64K, two
13
+ # cycles a byte, stretched the same way.
14
+ LIMIT = 100 + 0x40000
15
+
16
+ # Runs the machine on, through its own #cycle!, to the settle point,
17
+ # and returns the cycles that took. A settled machine runs none.
18
+ def settle_for_snapshot!
19
+ ran = 0
20
+ while ran < LIMIT && !settled_for_snapshot?
21
+ cycle!
22
+ ran += 1
23
+ end
24
+ settle_chips_for_snapshot
25
+ ran
26
+ end
27
+ end
28
+ end
29
+ end
@@ -1,6 +1,8 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require "badline/machine/clocking"
4
+ require "badline/machine/keyboard_buffer"
5
+ require "badline/machine/settling"
4
6
 
5
7
  module Badline
6
8
  # = Machine
@@ -58,10 +60,11 @@ module Badline
58
60
  # VIC-20's is "pal", with the RAM expansion `ram` names
59
61
  # (Vic20::Bus::RAM_CONFIGURATIONS), unexpanded when nil. A C128's is
60
62
  # one of C128::Model::ALL, with its SID `sid_model` when not nil, built
61
- # for C64 mode.
63
+ # for C64 mode. :plus4 is refused until the Plus/4 runs.
62
64
  def self.build(family, model:, sid_model: nil, reu: nil, ram: nil)
63
65
  return vic20(model, ram) if family == :vic20
64
66
  return c128(model, sid_model, :c64) if family == :c128
67
+ raise ArgumentError, "the Plus/4 isn't emulated yet" if family == :plus4
65
68
  raise ArgumentError, "no machine family named #{family}" unless family == :c64
66
69
 
67
70
  profile = Model.named(model)
@@ -119,7 +122,7 @@ module Badline
119
122
 
120
123
  extension = File.extname(media).downcase
121
124
  return true if extension == ".sid"
122
- return false unless %w[.prg .p00].include?(extension)
125
+ return false unless Media::Extensions::PROGRAMS.include?(extension)
123
126
 
124
127
  [Media::BASIC_START, Media::BASIC_START - 1].include?(Storage::P00.load_address(media))
125
128
  end
@@ -4,7 +4,8 @@ module Badline
4
4
  module Media
5
5
  # A C128 boot disk. At power-on the C128 KERNAL reads track 1, sector 0
6
6
  # of the disk in device 8 and runs it when it starts with "CBM", as boot
7
- # games have it. The 1581's DOS first loads and runs a USR file named
7
+ # games have it, from a sector image or from the raw GCR of a .g64 or
8
+ # .g71. The 1581's DOS first loads and runs a USR file named
8
9
  # COPYRIGHT CBM 86 when the disk has one, which is how CP/M's 1581 disks
9
10
  # hand the KERNAL their boot sector, so a .d81 with that file boots too.
10
11
  #
@@ -15,7 +16,7 @@ module Badline
15
16
  module BootDisk
16
17
  SIGNATURE = "CBM".bytes.freeze
17
18
  AUTO_BOOT = "copyright cbm 86"
18
- TYPES = Extensions.of(%i[disk])
19
+ TYPES = Extensions.of(%i[disk gcr])
19
20
 
20
21
  class << self
21
22
  def takes?(computer, path)
@@ -37,6 +38,7 @@ module Badline
37
38
  def boot?(path)
38
39
  type = File.extname(path).downcase
39
40
  return false unless TYPES.include?(type) && File.file?(path)
41
+ return gcr_signature(path) == SIGNATURE if Extensions.kind(path) == :gcr
40
42
 
41
43
  image = Media.open_storage(path, { read_only: true })
42
44
  block = image.read_block(1, 0)
@@ -44,6 +46,16 @@ module Badline
44
46
 
45
47
  type == ".d81" && !image.read_file(AUTO_BOOT, type: :usr).nil?
46
48
  end
49
+
50
+ private
51
+
52
+ # The first three bytes of track 1, sector 0 as the drive reads them
53
+ # off the GCR of a .g64 or .g71, or nil when the sector isn't there.
54
+ def gcr_signature(path)
55
+ track = Drives::Model1541::Disk.open(path, read_only: true).track(2)
56
+ data = track && Drives::Model1541::SectorReader.read(track.bytes, 1)[0]&.data
57
+ data && data[1, 3]
58
+ end
47
59
  end
48
60
  end
49
61
  end
@@ -28,6 +28,16 @@ module Badline
28
28
 
29
29
  # What goes in device 8 through the traps.
30
30
  MOUNTABLE = of(%i[disk archive])
31
+
32
+ # What goes in device 8, a true drive's or the traps', and the lists
33
+ # of disks to put there.
34
+ DEVICE8 = of(%i[disk gcr archive disk_list])
35
+
36
+ # Programs, which load on their own.
37
+ PROGRAMS = %w[.prg .p00].freeze
38
+
39
+ # Every medium Media.attach takes by its extension.
40
+ ALL = (KINDS.keys + PROGRAMS).freeze
31
41
  end
32
42
  end
33
43
  end
@@ -29,13 +29,10 @@ module Badline
29
29
  Array(bytes).each_with_index { |b, i| poke(addr + i, b) }
30
30
  end
31
31
 
32
- def save_state(out)
33
- out.blob(@storage)
34
- end
32
+ # Every byte as one record of a keyed snapshot module, under `key`.
33
+ def save_fields(out, key) = out.bytes(key, @storage)
35
34
 
36
- def load_state(input)
37
- input.blob_into(@storage)
38
- end
35
+ def load_fields(input, key) = input.bytes_into(key, @storage)
39
36
 
40
37
  # Refills the whole memory as if newly built with `initial`
41
38
  def clear!(initial = [])
@@ -12,13 +12,14 @@ module Badline
12
12
 
13
13
  private
14
14
 
15
- # Takes `sid`, `c64`, `vic20` or `c128` when given first.
15
+ # Takes `sid`, `c64`, `vic20`, `c128` or `plus4` when given first.
16
16
  def subcommand(args)
17
17
  case args.first
18
18
  when "sid" then @sid_command = true
19
19
  when "c64" then @family = :c64
20
20
  when "vic20" then @family = :vic20
21
21
  when "c128" then @family = :c128
22
+ when "plus4" then @family = :plus4
22
23
  else return
23
24
  end
24
25
  @family_named = !@sid_command
@@ -37,7 +38,9 @@ module Badline
37
38
  # the C128's.
38
39
  def pick_family
39
40
  return if @family_named || @sid_command || @media_path.nil?
40
- return unless %w[.prg .p00].include?(File.extname(@media_path).downcase) && File.file?(@media_path)
41
+
42
+ program = Media::Extensions::PROGRAMS.include?(File.extname(@media_path).downcase)
43
+ return unless program && File.file?(@media_path)
41
44
 
42
45
  @family = :c128 if Storage::P00.load_address(@media_path) == C128_BASIC_START
43
46
  end
@@ -35,6 +35,34 @@ module Badline
35
35
 
36
36
  def describe(option) = @native && option.name == "--sound" ? "#{option.text} (default)" : option.text
37
37
 
38
+ # The media the window opens, by their extensions (Media::Extensions),
39
+ # wrapped as the banner is.
40
+ def media_kinds
41
+ text = "Media can be a #{Media::Extensions::PROGRAMS.join('/')} program, a #{listed(%i[disk])} disk image, " \
42
+ "a #{listed(%i[gcr])} disk image for a true drive, an #{listed(%i[disk_list])} list of disk images, " \
43
+ "a #{listed(%i[archive])} tape archive, a #{listed(%i[tape])} tape, a #{listed(%i[cartridge])} " \
44
+ "cartridge, a #{listed(%i[tune])} tune, a .vsf snapshot, or a directory to mount as device 8."
45
+ wrap(text)
46
+ end
47
+
48
+ def listed(kinds) = Media::Extensions.of(kinds).join("/")
49
+
50
+ # The words of `text` in lines of at most 70 characters.
51
+ def wrap(text)
52
+ lines = [""]
53
+ text.split.each do |word|
54
+ line = lines.last
55
+ if line.empty?
56
+ lines[-1] = word
57
+ elsif line.size + word.size < 70
58
+ lines[-1] = "#{line} #{word}"
59
+ else
60
+ lines << word
61
+ end
62
+ end
63
+ lines.join("\n")
64
+ end
65
+
38
66
  def banner
39
67
  <<~BANNER
40
68
  Usage: #{@program} [c64] [options] [media]
@@ -50,11 +78,8 @@ module Badline
50
78
  c128 Run a C128 in C128 mode, the one --model names
51
79
  sid Play .sid tunes and directories of them
52
80
 
53
- Media can be a .prg/.p00 program, a .d64/.d71/.d81 disk image, a
54
- .g64 disk image for the true 1541, an .m3u or .vfl list of disk
55
- images, a .t64 tape archive, a .tap tape, a .crt cartridge, a .sid
56
- tune, a .vsf snapshot, or a directory to mount as device 8. It
57
- opens in the emulator window. There F11 quicksaves the machine to
81
+ #{media_kinds}
82
+ It opens in the emulator window. There F11 quicksaves the machine to
58
83
  the next of five slots in badline's data folder, replacing the
59
84
  oldest, and F12 restores the newest quicksave or named save. The
60
85
  window also autosaves every two minutes of play, which only the
@@ -85,17 +110,18 @@ module Badline
85
110
 
86
111
  --at and --script run events once a number of frames has run, as
87
112
  in --at 3500:key=space or --at 250,500:screenshot=shot%05d.bmp.
88
- key=NAME presses a C64 key (space, return, a, f1, run_stop, restore)
89
- or a joystick's direction or fire (joy1-up, joy2-fire) for #{Event::HOLD}
90
- frames. type=TEXT types it, with \\n for RETURN. insert=FILE swaps
91
- in a disk, a list's first disk, a tape or a cartridge, and
92
- eject=disk, tape or cartridge takes one out. screenshot=FILE saves
93
- the frame as a .bmp, with the frame's number in place of %d. menu
94
- opens the pause menu, and menu=PAGE opens it on snapshots, drive,
95
- datasette, expansion, ports, sound or power. Its frames still count,
96
- and resume closes it. display=vdc or display=vic shows the C128's
97
- 80 or 40 column screen, as F8 switches them. reset, freeze and quit
98
- take no argument.
113
+ key=NAME presses a key (space, return, a, f1, run_stop, restore, and
114
+ on the C128 esc, help, keypad1 and the rest) or a joystick's
115
+ direction or fire (joy1-up, joy2-fire) for #{Event::HOLD} frames, and
116
+ pushes the C128's caps_lock or forty_eighty, which lock. type=TEXT
117
+ types it, with \\n for RETURN. insert=FILE swaps in a disk, a list's
118
+ first disk, a tape or a cartridge, and eject=disk, tape or
119
+ cartridge takes one out. screenshot=FILE saves the frame as a .bmp,
120
+ with the frame's number in place of %d. menu opens the pause menu,
121
+ and menu=PAGE opens it on snapshots, drive, datasette, expansion,
122
+ ports, sound or power. Its frames still count, and resume closes
123
+ it. display=vdc or display=vic shows the C128's 80 or 40 column
124
+ screen, as F8 switches them. reset, freeze and quit take no argument.
99
125
  BANNER
100
126
  end
101
127
 
@@ -75,6 +75,8 @@ module Badline
75
75
  section: "Testing options", needs: :window),
76
76
  Option.new("--save-snapshot FILE", "Save the machine as a .vsf snapshot after the last frame",
77
77
  section: "Testing options", needs: :window),
78
+ Option.new("--vice", "Add VICE's modules to --save-snapshot's C64 snapshot, for x64sc",
79
+ section: "Testing options", needs: :window),
78
80
  Option.new("--at FRAME:EVENT", "Run EVENT once FRAME frames have run (see above)",
79
81
  section: "Testing options", needs: :window),
80
82
  Option.new("--script FILE", "Run the events in FILE, one FRAME:EVENT a line",
@@ -59,6 +59,8 @@ module Badline
59
59
  end
60
60
 
61
61
  def validate_family
62
+ raise Error, "the Plus/4 isn't emulated yet" if @family == :plus4
63
+
62
64
  if @family == :vic20
63
65
  validate_vic20
64
66
  elsif !@ram.nil?