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
@@ -38,14 +38,11 @@ module Badline
38
38
  @host = @bus.host_lines
39
39
  end
40
40
 
41
- # The host's lines as last latched. The jumpers and the bus are the
42
- # drive's wiring.
43
- def save_state(out)
44
- out.int(@host)
45
- end
41
+ # The host's lines as the port latched them, under `prefix`.
42
+ def save_fields(out, prefix) = out.int("#{prefix}host", @host)
46
43
 
47
- def load_state(input)
48
- @host = input.int
44
+ def load_fields(input, prefix)
45
+ @host = input.int("#{prefix}host", @host)
49
46
  end
50
47
 
51
48
  # Whether the host has moved ATN since the last latch_host.
@@ -38,19 +38,6 @@ module Badline
38
38
  init_watch
39
39
  end
40
40
 
41
- # The last byte on the data bus and the RAM. What a watched stretch
42
- # touched is Idle's, and a restored bus isn't watching.
43
- def save_state(out)
44
- out.int(@data)
45
- @ram.save_state(out)
46
- end
47
-
48
- def load_state(input)
49
- @data = input.int
50
- @ram.load_state(input)
51
- @watching = false
52
- end
53
-
54
41
  def peek(addr)
55
42
  return @data = @rom.peek(addr | 0x4000) if addr >= 0x8000
56
43
 
@@ -1,7 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require "badline/media/extensions"
3
4
  require "badline/drives/model1541/sector_reader"
4
- require "badline/drives/model1541/disk/saved_state"
5
5
 
6
6
  module Badline
7
7
  module Drives
@@ -23,7 +23,8 @@ module Badline
23
23
  # host file. The disk is write-protected when the image won't take
24
24
  # writes.
25
25
  class Disk
26
- include SavedState
26
+ # Whether the image is a .g64 or .g71, by its name.
27
+ def self.g64?(path) = Media::Extensions.kind(path) == :gcr
27
28
 
28
29
  MAX_HALF_TRACK = 84
29
30
 
@@ -61,16 +62,16 @@ module Badline
61
62
  # formatted. `read_only` opens the image write-protected.
62
63
  def self.open(path, read_only: false)
63
64
  image = image_for(path, read_only)
64
- (SavedState.g64?(path) ? from_g64(image) : from_d64(image)).opened(path, read_only)
65
+ (Disk.g64?(path) ? from_g64(image) : from_d64(image)).opened(path, read_only)
65
66
  end
66
67
 
67
68
  # The image at +path+, by its name: a .g64 or .g71, a .d71, and
68
- # anything else a .d64.
69
- def self.image_for(path, read_only)
70
- return Storage::G64Image.new(path, read_only:) if SavedState.g64?(path)
71
- return Storage::D71Image.new(path, read_only:) if File.extname(path).casecmp?(".d71")
69
+ # anything else a .d64. `bytes` stands in for the file's contents.
70
+ def self.image_for(path, read_only, bytes = nil)
71
+ return Storage::G64Image.new(path, read_only:, bytes:) if Disk.g64?(path)
72
+ return Storage::D71Image.new(path, read_only:, bytes:) if File.extname(path).casecmp?(".d71")
72
73
 
73
- Storage::D64Image.new(path, read_only:)
74
+ Storage::D64Image.new(path, read_only:, bytes:)
74
75
  end
75
76
 
76
77
  # A disk from a G64 or G71 image, each half track as the image stores
@@ -0,0 +1,251 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Badline
4
+ module Drives
5
+ class Model1541
6
+ class Disk
7
+ # The kinds of image a disk embeds, by name.
8
+ IMAGE_KINDS = %w[none d64 d71 g64].freeze
9
+
10
+ # A disk's fields in a keyed snapshot module (BL-D8-DISK): the image
11
+ # file's path and how it was opened, the image as last flushed,
12
+ # embedded under image and image.kind, each half track that differs
13
+ # from what the image decodes to, as track<H>.zone, track<H>.speeds
14
+ # and track<H>.gcr, raw GCR as the head sees it, and the half tracks
15
+ # written since the last flush.
16
+ module KeyedState
17
+ # The image's path, expanded, and whether it was opened read-only,
18
+ # for a disk from Disk.open. Nil for a disk made another way.
19
+ attr_reader :path, :read_only
20
+
21
+ # Notes the image's path and how it was opened.
22
+ def opened(path, read_only)
23
+ @path = File.expand_path(path)
24
+ @read_only = read_only
25
+ self
26
+ end
27
+
28
+ def save_fields(out)
29
+ path = @path
30
+ out.text("path", path) if path
31
+ out.bool("read_only", @read_only == true)
32
+ image = @image
33
+ decoded = nil
34
+ if image
35
+ out.text("image.kind", Disk.image_kind(image)).text("image.path", image.path.to_s)
36
+ out.bytes("image", image.contents)
37
+ decoded = Disk.decode(image)
38
+ end
39
+ @tracks.each_with_index { |track, half| save_track_fields(out, half, track, decoded) }
40
+ out.ints("written", @written.keys)
41
+ end
42
+
43
+ def load_track_fields(input)
44
+ @tracks.each_index do |half|
45
+ zone = input.optional_int("track#{half}.zone")
46
+ next if zone.nil?
47
+
48
+ speeds = input.ints("track#{half}.speeds", [])
49
+ gcr = input.bytes("track#{half}.gcr", "".b).bytes
50
+ @tracks[half] = Track.new(gcr, zone, speeds.empty? ? nil : speeds.dup)
51
+ end
52
+ @written.clear
53
+ input.ints("written", []).each { |half| @written[half] = true }
54
+ self
55
+ end
56
+
57
+ private
58
+
59
+ def save_track_fields(out, half, track, decoded)
60
+ return if track.nil?
61
+
62
+ original = decoded&.track(half)
63
+ return if original && original.zone == track.zone && original.speeds == track.speeds &&
64
+ original.bytes == track.bytes
65
+
66
+ out.int("track#{half}.zone", track.zone)
67
+ speeds = track.speeds
68
+ out.ints("track#{half}.speeds", speeds) if speeds
69
+ out.bytes("track#{half}.gcr", track.bytes)
70
+ end
71
+ end
72
+
73
+ include KeyedState
74
+
75
+ # The kind of `image`, by name.
76
+ def self.image_kind(image)
77
+ return "g64" if image.is_a?(Storage::G64Image)
78
+ return "d71" if image.is_a?(Storage::D71Image)
79
+
80
+ "d64"
81
+ end
82
+
83
+ # The tracks `image` holds, as an insert decodes them.
84
+ def self.decode(image) = image.is_a?(Storage::G64Image) ? from_g64(image) : from_d64(image)
85
+
86
+ # The disk a keyed snapshot module describes: its embedded image
87
+ # decoded, with the tracks the module holds laid over it. The image
88
+ # takes writes back to its file only while that file is there and
89
+ # the reader isn't `detached`. With no image, the disk is the
90
+ # tracks alone, write-protected.
91
+ def self.from_fields(input, detached: false)
92
+ path = input.text("path", "")
93
+ read_only = input.bool("read_only", false)
94
+ kind = input.choice("image.kind", IMAGE_KINDS, "none")
95
+ image_path = input.text("image.path", "")
96
+ disk = kind == "none" ? Disk.new : from_image(input, kind, image_path, read_only || detached)
97
+ disk.opened(path, read_only || kind == "none") unless path.empty?
98
+ disk.load_track_fields(input)
99
+ end
100
+
101
+ def self.from_image(input, kind, path, read_only)
102
+ bytes = input.bytes("image", "".b).bytes
103
+ writable_path = path.empty? || !File.file?(path) ? "" : path
104
+ image = case kind
105
+ when "g64" then Storage::G64Image.new(writable_path, read_only:, bytes:)
106
+ when "d71" then Storage::D71Image.new(writable_path, read_only:, bytes:)
107
+ else Storage::D64Image.new(writable_path, read_only:, bytes:)
108
+ end
109
+ decode(image)
110
+ end
111
+ end
112
+
113
+ class Mechanism
114
+ # The head, the motor, the stepper and the read and write
115
+ # electronics in a keyed snapshot module (BL-D8-MECH): where the
116
+ # head is, the disk's place in its turn, the read clock's next
117
+ # clock in cycles from now, the shift registers and the flags. The
118
+ # cell under the head, its width and the next event come from the
119
+ # track and the time on load, the cell period from the zone, and
120
+ # the write protection from the disk.
121
+ module KeyedState
122
+ def save_fields(out)
123
+ out.int("half_track", @half_track).int("slip", @slip).int("side", @side_base).int("zone", @zone)
124
+ out.int("time", @time).int("read_clock_in", @clock_at - @time).int("clocks", @clocks)
125
+ out.int("shift", @shift).int("ones", @ones).int("bits", @bits).int("write_shift", @write_shift)
126
+ out.bool("motor", @motor).bool("led", @led).bool("sync", @sync).bool("byte_ready", @byte_ready)
127
+ out.bool("byte_latched", @byte_latched).bool("writing", @writing).bool("write_gate", @write_gate)
128
+ out.bool("track_written", @track_written).optional_int("write_index", @write_index)
129
+ end
130
+
131
+ # Puts `disk` under the head without flushing the one there, as a
132
+ # snapshot load replaces it.
133
+ def load_fields(input, disk)
134
+ @disk = disk
135
+ load_head_fields(input)
136
+ load_flag_fields(input)
137
+ @clock = Track.cell(@zone)
138
+ @protected = disk&.write_protected? || false
139
+ track_written = input.bool("track_written", false)
140
+ write_index = input.optional_int("write_index")
141
+ load_track(@time)
142
+ @track_written = track_written
143
+ @write_index = write_index
144
+ end
145
+
146
+ private
147
+
148
+ def load_head_fields(input)
149
+ @half_track = input.int("half_track", @half_track)
150
+ @slip = input.masked("slip", @slip, 0x03)
151
+ @side_base = input.int("side", @side_base)
152
+ @zone = input.masked("zone", @zone, 0x03)
153
+ @time = input.int("time", @time)
154
+ @clock_at = @time + input.int("read_clock_in", @clock_at - @time)
155
+ @clocks = input.int("clocks", @clocks)
156
+ @shift = input.int("shift", @shift)
157
+ @ones = input.int("ones", @ones)
158
+ @bits = input.int("bits", @bits)
159
+ @write_shift = input.int("write_shift", @write_shift)
160
+ end
161
+
162
+ def load_flag_fields(input)
163
+ @motor = input.bool("motor", @motor)
164
+ @led = input.bool("led", @led)
165
+ @sync = input.bool("sync", @sync)
166
+ @byte_ready = input.bool("byte_ready", @byte_ready)
167
+ @byte_latched = input.bool("byte_latched", @byte_latched)
168
+ @writing = input.bool("writing", @writing)
169
+ @write_gate = input.bool("write_gate", @write_gate)
170
+ end
171
+ end
172
+
173
+ include KeyedState
174
+ end
175
+
176
+ class Bus
177
+ # The drive's RAM and the byte last on its data bus, in a keyed
178
+ # snapshot module (BL-D8-RAM).
179
+ def save_fields(out)
180
+ out.int("data", @data).bytes("ram", @ram.read(0, @ram.length))
181
+ end
182
+
183
+ def load_fields(input)
184
+ @data = input.byte("data", @data)
185
+ ram = @ram.read(0, @ram.length)
186
+ @ram.write(0, input.bytes_into("ram", ram))
187
+ @watching = false
188
+ end
189
+ end
190
+
191
+ # The drive's own fields in a keyed snapshot module (BL-D8): its model,
192
+ # its clock phase and cycle count, the SO pulse due, and the host's
193
+ # lines as the serial port latched them. A save settles the drive first, so it is awake;
194
+ # a load forgets its orbits.
195
+ module KeyedState
196
+ def save_fields(out)
197
+ settle!
198
+ out.text("model", model_name).int("phase", @phase).int("cycles", @cycles)
199
+ out.bool("so_pending", @mechanism.so_pending)
200
+ @serial_port.save_fields(out, "serial.")
201
+ out
202
+ end
203
+
204
+ def load_fields(input)
205
+ settle!
206
+ input.choice("model", [model_name], model_name)
207
+ @phase = input.int("phase", @phase)
208
+ @cycles = input.int("cycles", @cycles)
209
+ @mechanism.so_pending = input.bool("so_pending", @mechanism.so_pending)
210
+ @serial_port.load_fields(input, "serial.")
211
+ end
212
+
213
+ def restored! = forget_orbits
214
+
215
+ # The drive as modules of its own, named from `name` (BL-D8 for
216
+ # device 8): its own fields, its RAM, each VIA, the CPU, the
217
+ # mechanism and the disk, when one is in.
218
+ def save_modules(sections, name)
219
+ sections << Snapshot::Keyed.section(name) { |out| save_fields(out) }
220
+ sections << Snapshot::Keyed.section("#{name}-RAM") { |out| @bus.save_fields(out) }
221
+ sections << Snapshot::Keyed.section("#{name}-VIA1") { |out| @via1.save_fields(out) }
222
+ sections << Snapshot::Keyed.section("#{name}-VIA2") { |out| @via2.save_fields(out) }
223
+ sections << Snapshot::Keyed.section("#{name}-CPU") { |out| @cpu.save_fields(out) }
224
+ sections << Snapshot::Keyed.section("#{name}-MECH") { |out| @mechanism.save_fields(out) }
225
+ disk = @mechanism.disk
226
+ sections << Snapshot::Keyed.section("#{name}-DISK") { |out| disk.save_fields(out) } if disk
227
+ sections
228
+ end
229
+
230
+ # The drive from its modules, `input` being its own. No disk module
231
+ # means no disk in the drive.
232
+ def load_modules(modules, input, name)
233
+ load_fields(input)
234
+ @bus.load_fields(modules.required("#{name}-RAM"))
235
+ @via1.load_fields(modules.required("#{name}-VIA1"))
236
+ @via2.load_fields(modules.required("#{name}-VIA2"))
237
+ @cpu.load_fields(modules.required("#{name}-CPU"))
238
+ disk = modules.optional("#{name}-DISK")
239
+ mechanism = modules.required("#{name}-MECH")
240
+ @mechanism.load_fields(mechanism, disk.nil? ? nil : Disk.from_fields(disk, detached: modules.detached?))
241
+ @via1.restored!
242
+ @via2.restored!
243
+ @cpu.restored!
244
+ restored!
245
+ end
246
+ end
247
+
248
+ include KeyedState
249
+ end
250
+ end
251
+ end
@@ -1,7 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require "badline/drives/model1541/rotation"
4
- require "badline/drives/model1541/mechanism/saved_state"
5
4
  require "badline/drives/model1541/byte_ready"
6
5
 
7
6
  module Badline
@@ -71,7 +70,6 @@ module Badline
71
70
  # its track 0 sensor looks for the head (Mechanism.new's +slip+).
72
71
  class Mechanism
73
72
  include Rotation
74
- include SavedState
75
73
  include ByteReady
76
74
 
77
75
  MOTOR = 0x04
@@ -9,7 +9,7 @@ require "badline/drives/model1541/track"
9
9
  require "badline/drives/model1541/disk"
10
10
  require "badline/drives/model1541/mechanism"
11
11
  require "badline/drives/model1541/disk_via"
12
- require "badline/drives/model1541/saved_state"
12
+ require "badline/drives/model1541/keyed_state"
13
13
 
14
14
  module Badline
15
15
  module Drives
@@ -19,7 +19,6 @@ module Badline
19
19
  class Model1541
20
20
  include Engine::Core
21
21
  include Engine::VIAs
22
- include SavedState
23
22
 
24
23
  CLOCK_HZ = 1_000_000
25
24
 
@@ -40,19 +40,6 @@ module Badline
40
40
  init_watch
41
41
  end
42
42
 
43
- # The last byte on the data bus and the RAM. What a watched stretch
44
- # touched is Idle's, and a restored bus isn't watching.
45
- def save_state(out)
46
- out.int(@data)
47
- @ram.save_state(out)
48
- end
49
-
50
- def load_state(input)
51
- @data = input.int
52
- @ram.load_state(input)
53
- @watching = false
54
- end
55
-
56
43
  def peek(addr)
57
44
  return @data = @rom.peek(addr) if addr >= 0x8000
58
45
  return @data = chip_peek(addr) if addr >= 0x2000
@@ -0,0 +1,108 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Badline
4
+ module Drives
5
+ class Model1571
6
+ class WD1770
7
+ # The controller's registers, status and result in a keyed snapshot
8
+ # module (BL-D8-FDC), and how many of the drive CPU's cycles until a
9
+ # command under way finishes. It loads after the CPU, which that
10
+ # counts from.
11
+ def save_fields(out)
12
+ out.ints("registers", @registers).int("status", @status).int("result", @result)
13
+ out.int("due_in", @due - @cpu.cycles)
14
+ end
15
+
16
+ def load_fields(input)
17
+ input.ints_into("registers", @registers)
18
+ @status = input.byte("status", @status)
19
+ @result = input.int("result", @result)
20
+ @due = @cpu.cycles + input.int("due_in", @due - @cpu.cycles)
21
+ end
22
+ end
23
+
24
+ class Bus
25
+ # The drive's RAM and the byte last on its data bus, in a keyed
26
+ # snapshot module (BL-D8-RAM).
27
+ def save_fields(out)
28
+ out.int("data", @data).bytes("ram", @ram.read(0, @ram.length))
29
+ end
30
+
31
+ def load_fields(input)
32
+ @data = input.byte("data", @data)
33
+ ram = @ram.read(0, @ram.length)
34
+ @ram.write(0, input.bytes_into("ram", ram))
35
+ @watching = false
36
+ end
37
+ end
38
+
39
+ # The drive's own fields in a keyed snapshot module (BL-D8), as the
40
+ # 1541's. The clock rate, the side and the fast serial direction come
41
+ # from VIA 1's port A once the VIAs are loaded.
42
+ module KeyedState
43
+ def save_fields(out)
44
+ settle!
45
+ out.text("model", model_name).int("phase", @phase).int("cycles", @cycles)
46
+ out.bool("so_pending", @mechanism.so_pending)
47
+ @serial_port.save_fields(out, "serial.")
48
+ out
49
+ end
50
+
51
+ def load_fields(input)
52
+ settle!
53
+ input.choice("model", [model_name], model_name)
54
+ @phase = input.int("phase", @phase)
55
+ @cycles = input.int("cycles", @cycles)
56
+ @mechanism.so_pending = input.bool("so_pending", @mechanism.so_pending)
57
+ @serial_port.load_fields(input, "serial.")
58
+ end
59
+
60
+ def restored!
61
+ @fast_serial_out = @via1.port_a_output.anybits?(FAST_SERIAL_OUT)
62
+ port_a_written(@via1.port_a_output)
63
+ push_fast_output(@fast_serial_out ? fast_pins : 0)
64
+ forget_orbits
65
+ end
66
+
67
+ # The drive as modules of its own, as the 1541's, with the CIA and
68
+ # the WD1770.
69
+ def save_modules(sections, name)
70
+ sections << Snapshot::Keyed.section(name) { |out| save_fields(out) }
71
+ sections << Snapshot::Keyed.section("#{name}-RAM") { |out| @bus.save_fields(out) }
72
+ sections << Snapshot::Keyed.section("#{name}-VIA1") { |out| @via1.save_fields(out) }
73
+ sections << Snapshot::Keyed.section("#{name}-VIA2") { |out| @via2.save_fields(out) }
74
+ sections << Snapshot::Keyed.section("#{name}-CIA") { |out| @cia.save_fields(out) }
75
+ sections << Snapshot::Keyed.section("#{name}-CPU") { |out| @cpu.save_fields(out) }
76
+ sections << Snapshot::Keyed.section("#{name}-FDC") { |out| @fdc.save_fields(out) }
77
+ sections << Snapshot::Keyed.section("#{name}-MECH") { |out| @mechanism.save_fields(out) }
78
+ disk = @mechanism.disk
79
+ sections << Snapshot::Keyed.section("#{name}-DISK") { |out| disk.save_fields(out) } if disk
80
+ sections
81
+ end
82
+
83
+ # The drive from its modules, `input` being its own. The WD1770
84
+ # counts from the CPU's cycles, so it loads after the CPU.
85
+ def load_modules(modules, input, name)
86
+ load_fields(input)
87
+ @bus.load_fields(modules.required("#{name}-RAM"))
88
+ @via1.load_fields(modules.required("#{name}-VIA1"))
89
+ @via2.load_fields(modules.required("#{name}-VIA2"))
90
+ @cia.load_fields(modules.required("#{name}-CIA"))
91
+ @cpu.load_fields(modules.required("#{name}-CPU"))
92
+ @fdc.load_fields(modules.required("#{name}-FDC"))
93
+ disk = modules.optional("#{name}-DISK")
94
+ mechanism = modules.required("#{name}-MECH")
95
+ @mechanism.load_fields(mechanism,
96
+ disk.nil? ? nil : Model1541::Disk.from_fields(disk, detached: modules.detached?))
97
+ @via1.restored!
98
+ @via2.restored!
99
+ @cia.restored!
100
+ @cpu.restored!
101
+ restored!
102
+ end
103
+ end
104
+
105
+ include KeyedState
106
+ end
107
+ end
108
+ end
@@ -54,20 +54,6 @@ module Badline
54
54
  offset.zero? ? command(value) : @registers[offset] = value
55
55
  end
56
56
 
57
- def save_state(out)
58
- out.ints([*@registers, @status, @due, @result])
59
- end
60
-
61
- # A state from before the controller ran commands holds the four
62
- # registers alone.
63
- def load_state(input)
64
- values = input.ints
65
- @registers = values[0, 4]
66
- @status = values[4] || 0
67
- @due = values[5] || 0
68
- @result = values[6] || 0
69
- end
70
-
71
57
  private
72
58
 
73
59
  def command(value)
@@ -5,7 +5,7 @@ require "badline/drives/model1571/wd1770"
5
5
  require "badline/drives/model1571/serial_port"
6
6
  require "badline/drives/model1571/serial_via"
7
7
  require "badline/drives/model1571/bus"
8
- require "badline/drives/model1571/saved_state"
8
+ require "badline/drives/model1571/keyed_state"
9
9
 
10
10
  module Badline
11
11
  module Drives
@@ -28,7 +28,6 @@ module Badline
28
28
  include Engine::Core
29
29
  include Engine::VIAs
30
30
  include Engine::FastSerial
31
- include SavedState
32
31
 
33
32
  CLOCK_HZ = 1_000_000
34
33
  FAST_CLOCK_HZ = 2_000_000
@@ -41,19 +41,6 @@ module Badline
41
41
  @touched_timers = 0
42
42
  end
43
43
 
44
- # The last byte on the data bus and the RAM. What a watched stretch
45
- # touched is Idle's, and a restored bus isn't watching.
46
- def save_state(out)
47
- out.int(@data)
48
- @ram.save_state(out)
49
- end
50
-
51
- def load_state(input)
52
- @data = input.int
53
- @ram.load_state(input)
54
- @watching = false
55
- end
56
-
57
44
  def peek(addr)
58
45
  return @data = @rom.peek(addr) if addr >= 0x8000
59
46
  return @data = chip_peek(addr) if addr >= 0x2000
@@ -1,7 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "badline/drives/model1581/disk/saved_state"
4
-
5
3
  module Badline
6
4
  module Drives
7
5
  class Model1581
@@ -24,8 +22,6 @@ module Badline
24
22
  # file. A track formatted another way stays on the disk alone. The disk
25
23
  # is write-protected when the image won't take writes.
26
24
  class Disk
27
- include SavedState
28
-
29
25
  CYLINDERS = 80
30
26
  SIDES = 2
31
27