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
@@ -0,0 +1,117 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Badline
4
+ class C128
5
+ # The whole machine as keyed snapshot modules (Snapshot::Keyed), in the
6
+ # order a load applies them, as C64::KeyedModules: BL-MACHINE with the
7
+ # last bus access under bus.*, the RAM, both banks of colour RAM, the
8
+ # MMU, the cartridge, the 8502 with its port, the Z80, the chips, the
9
+ # datasette, device 8 through the traps and the true drives. The
10
+ # machine's own fields load after the Z80's, which z80_ahead counts
11
+ # from.
12
+ module KeyedModules
13
+ def save_modules
14
+ bus = @bus
15
+ sections = [Snapshot::Keyed.section("BL-MACHINE") do |out|
16
+ save_machine_fields(out)
17
+ bus.save_fields(out, "bus.")
18
+ end]
19
+ sections << Snapshot::Keyed.section("BL-RAM") { |out| bus.ram.save_fields(out, "ram") }
20
+ sections << Snapshot::Keyed.section("BL-COLORRAM") { |out| bus.save_color_fields(out) }
21
+ sections << Snapshot::Keyed.section("BL-MMU") { |out| bus.mmu.save_fields(out) }
22
+ cartridge = bus.cartridge
23
+ sections << Snapshot::Keyed.section("BL-CART") { |out| cartridge.save_fields(out) } if cartridge
24
+ save_chip_modules(sections, bus)
25
+ sections << Snapshot::Keyed.section("BL-TAPE") { |out| bus.datasette.save_fields(out) }
26
+ sections << Snapshot::Keyed.section("BL-TRAPDOS") { |out| save_trap_dos_fields(out) } if mounted?
27
+ save_drive_modules(sections)
28
+ end
29
+
30
+ # Raises Snapshot::FormatError for the modules of another model, SID
31
+ # or mode.
32
+ def load_modules(modules)
33
+ machine = modules.machine
34
+ check_snapshot_setup(Snapshot::C128Setup.read_fields(machine))
35
+ bus = @bus
36
+ bus.ram.load_fields(modules.required("BL-RAM"), "ram")
37
+ bus.load_color_fields(modules.required("BL-COLORRAM"))
38
+ bus.mmu.load_fields(modules.required("BL-MMU"))
39
+ load_cartridge_module(modules.optional("BL-CART"))
40
+ load_chip_modules(modules)
41
+ bus.load_fields(machine, "bus.")
42
+ load_machine_fields(machine)
43
+ bus.datasette.load_fields(modules.required("BL-TAPE"))
44
+ trap_dos = modules.optional("BL-TRAPDOS")
45
+ trap_dos ? load_trap_dos_fields(trap_dos, detached: modules.detached?) : unmount
46
+ load_drive_modules(modules)
47
+ modules_restored!
48
+ self
49
+ end
50
+
51
+ private
52
+
53
+ def save_chip_modules(sections, bus)
54
+ sections << Snapshot::Keyed.section("BL-CPU") do |out|
55
+ @cpu.save_fields(out)
56
+ bus.save_port_fields(out)
57
+ end
58
+ sections << Snapshot::Keyed.section("BL-Z80") { |out| @z80.save_fields(out) }
59
+ sections << Snapshot::Keyed.section("BL-VIC") { |out| @vic.save_fields(out) }
60
+ sections << Snapshot::Keyed.section("BL-VDC") { |out| @vdc.save_fields(out) }
61
+ sections << Snapshot::Keyed.section("BL-CIA1") { |out| @cia1.save_fields(out) }
62
+ sections << Snapshot::Keyed.section("BL-CIA2") { |out| @cia2.save_fields(out) }
63
+ sections << Snapshot::Keyed.section("BL-SID") { |out| @sid.save_fields(out) }
64
+ end
65
+
66
+ # The port goes in ahead of the chips, as the C64's does, and the bus
67
+ # maps its banks and the keyboard's extra rows once they are in. That
68
+ # hands the bus to the CPU the MMU names, which resets the Z80's
69
+ # schedule and can reset the 8502, so the CPUs, the last bus access
70
+ # and the machine's own fields load after it.
71
+ def load_chip_modules(modules)
72
+ cpu = modules.required("BL-CPU")
73
+ @bus.load_port_fields(cpu)
74
+ @vic.load_fields(modules.required("BL-VIC"))
75
+ @vdc.load_fields(modules.required("BL-VDC"))
76
+ @cia1.load_fields(modules.required("BL-CIA1"))
77
+ @cia2.load_fields(modules.required("BL-CIA2"))
78
+ @sid.load_fields(modules.required("BL-SID"))
79
+ @bus.restored!
80
+ @cpu.load_fields(cpu)
81
+ @z80.load_fields(modules.required("BL-Z80"))
82
+ end
83
+
84
+ # The cartridge comes back built afresh from the CRT image the module
85
+ # holds, before its state goes in. No module means no cartridge.
86
+ def load_cartridge_module(input)
87
+ if input
88
+ cartridge = C64::Cartridge.from_fields(input)
89
+ connect_cartridge(cartridge)
90
+ cartridge.load_fields(input)
91
+ elsif @bus.cartridge
92
+ @bus.detach_cartridge
93
+ end
94
+ end
95
+
96
+ def modules_restored!
97
+ @cpu.restored!
98
+ @z80.restored!
99
+ @vic.restored!
100
+ @vdc.restored!
101
+ @cia1.restored!
102
+ @cia2.restored!
103
+ @sid.restored!
104
+ machine_restored!
105
+ end
106
+
107
+ def check_snapshot_setup(setup)
108
+ ours = snapshot_setup
109
+ return if setup == ours
110
+
111
+ raise Snapshot::FormatError, "the snapshot is of a #{setup}, not a #{ours}"
112
+ end
113
+ end
114
+
115
+ include KeyedModules
116
+ end
117
+ end
@@ -0,0 +1,91 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Badline
4
+ class C128
5
+ # The machine's own fields in a keyed snapshot module (Snapshot::Keyed,
6
+ # BL-MACHINE): the family, its model, SID and mode by name, the cycle
7
+ # count, $D030's clock bits on their way, the NMI glue, the two
8
+ # schedulers' offsets (z80_ahead, the Z80's T-states past when it was
9
+ # due, and chips_ahead), an 8502 reset waiting for the bus, and under
10
+ # host.* the host's typing (the codes still to type, whether it is
11
+ # typing, and where Input::MatrixTypist is in a code on the key
12
+ # matrix), C= held from power-on and the on_init handlers still to run.
13
+ # They load after the Z80's own fields, which z80_ahead counts from.
14
+ module KeyedState
15
+ # How many more on_init handlers the machine a snapshot was taken of had
16
+ # yet to run than this one has. Nil until the machine is restored.
17
+ attr_reader :init_handlers_lost
18
+
19
+ # Whether a snapshot saves now (Machine::Settling): the 8502 between
20
+ # instructions or jammed. While the Z80 runs, the 8502 waits at the
21
+ # boundary it handed the bus over at, and the Z80 runs whole
22
+ # instructions.
23
+ def settled_for_snapshot? = @cpu.boundary? || @cpu.jammed?
24
+
25
+ # The SID's deferred cycles and writes, run up to this cycle, and the
26
+ # VIC's sprite collisions latched up to the beam.
27
+ def settle_chips_for_snapshot
28
+ sid.catch_up!
29
+ @vic.fold_collisions!
30
+ end
31
+
32
+ def save_machine_fields(out)
33
+ out.text("family", "c128").text("written_by", VERSION)
34
+ snapshot_setup.write_fields(out)
35
+ out.int("cycles", @cycles).int("clock_bits", @clock_bits).bool("nmi_asserted", @nmi_asserted)
36
+ out.bool("cartridge_nmi", @cartridge_nmi).bool("restore_pulse", @restore_pulse)
37
+ out.int("z80_ahead", @z80.cycles - @z80_due).int("chips_ahead", @chips_ahead)
38
+ out.bool("cpu_reset_pending", @cpu_reset_pending)
39
+ pending = @pending_keys
40
+ out.ints("host.pending_keys", pending) if pending
41
+ out.bool("host.typing", !pending.nil?)
42
+ out.int("host.init_pending", @cycles <= init_threshold ? @init_handlers.length : 0)
43
+ out.bool("host.holding_commodore", @holding_commodore)
44
+ @typist.save_fields(out, "host.typist.")
45
+ end
46
+
47
+ def load_machine_fields(input)
48
+ @cycles = input.int("cycles", @cycles)
49
+ @clock_bits = input.int("clock_bits", @clock_bits)
50
+ @nmi_asserted = input.bool("nmi_asserted", @nmi_asserted)
51
+ @cartridge_nmi = input.bool("cartridge_nmi", @cartridge_nmi)
52
+ @restore_pulse = input.bool("restore_pulse", @restore_pulse)
53
+ @z80_due = @z80.cycles - input.int("z80_ahead", @z80.cycles - @z80_due)
54
+ @chips_ahead = input.int("chips_ahead", @chips_ahead)
55
+ @cpu_reset_pending = input.bool("cpu_reset_pending", @cpu_reset_pending)
56
+ pending = input.ints("host.pending_keys", [])
57
+ @pending_keys = input.bool("host.typing", !pending.empty?) ? pending.dup : nil
58
+ @init_handlers_lost = [input.int("host.init_pending", 0) - @init_handlers.length, 0].max
59
+ load_holding_fields(input)
60
+ @typist.load_fields(input, "host.typist.")
61
+ end
62
+
63
+ # Which CPU has the bus and whose turn it is, from the MMU and the
64
+ # schedulers' offsets, and the serial bus's lines.
65
+ def machine_restored!
66
+ @z80_running = @bus.z80?
67
+ @z80_turn = @z80_running || @chips_ahead.positive?
68
+ push_serial_lines
69
+ push_fast_serial
70
+ end
71
+
72
+ private
73
+
74
+ # C= held from power-on until the machine reaches C64 mode
75
+ # (Modes#hold_commodore_key).
76
+ def load_holding_fields(input)
77
+ holding = input.bool("host.holding_commodore", @holding_commodore)
78
+ keyboard.release(:cbm) if @holding_commodore && !holding
79
+ keyboard.press(:cbm) if holding && !@holding_commodore
80
+ @holding_commodore = holding
81
+ end
82
+
83
+ # How this machine was built.
84
+ def snapshot_setup
85
+ Snapshot::C128Setup.new(model: @model.name, sid_model: @sid.model, mode: @c64_built ? :c64 : :c128)
86
+ end
87
+ end
88
+
89
+ include KeyedState
90
+ end
91
+ end
@@ -21,6 +21,11 @@ module Badline
21
21
  @bus.caps_lock = false
22
22
  end
23
23
 
24
+ # Pushes CAPS LOCK, down if it was up and up if it was down.
25
+ def push_caps_lock
26
+ @bus.caps_lock = !@bus.caps_lock
27
+ end
28
+
24
29
  # The 40/80 DISPLAY key locks down and up. The KERNAL reads it at reset
25
30
  # and starts the screen editor on the VDC's 80 columns while it is down.
26
31
  def press_display_key
@@ -30,6 +35,10 @@ module Badline
30
35
  def release_display_key
31
36
  @bus.mmu.display_key = false
32
37
  end
38
+
39
+ def push_display_key
40
+ @bus.mmu.display_key = !@bus.mmu.display_key
41
+ end
33
42
  end
34
43
  end
35
44
  end
@@ -151,16 +151,17 @@ module Badline
151
151
  end
152
152
  end
153
153
 
154
- # The registers and the P0H and P1H writes waiting for P0L and P1L.
155
- # The bus maps the restored registers once its own state is back.
156
- def save_state(out)
157
- out.ints(@registers).int(@p0h_latch).int(@p1h_latch)
154
+ # The registers and the page pointers' high bytes waiting for their
155
+ # low bytes, in a keyed snapshot module (Snapshot::Keyed). The bus
156
+ # maps its banks from them afterwards.
157
+ def save_fields(out)
158
+ out.bytes("registers", @registers).int("p0h_latch", @p0h_latch).int("p1h_latch", @p1h_latch)
158
159
  end
159
160
 
160
- def load_state(input)
161
- input.ints_into(@registers)
162
- @p0h_latch = input.int
163
- @p1h_latch = input.int
161
+ def load_fields(input)
162
+ input.bytes_into("registers", @registers)
163
+ @p0h_latch = input.masked("p0h_latch", @p0h_latch, 0x0f)
164
+ @p1h_latch = input.masked("p1h_latch", @p1h_latch, 0x0f)
164
165
  end
165
166
 
166
167
  # CR and the LCRs at $FF00-$FF04: an LCR reads its PCR.
@@ -46,6 +46,33 @@ module Badline
46
46
  end
47
47
  end
48
48
 
49
+ # The system ROM's byte at +addr+, $8000 or above, as CR value 0 maps it.
50
+ def system_rom_peek(addr)
51
+ if addr < 0xc000
52
+ @basic_high_rom.peek(addr)
53
+ elsif addr < 0xd000
54
+ @editor_rom.peek(addr)
55
+ elsif addr < 0xe000
56
+ @c128_character_rom.peek(addr)
57
+ else
58
+ @c128_kernal_rom.peek(addr)
59
+ end
60
+ end
61
+
62
+ # The chip's byte at +addr+, in $D000-$DFFF, as C128 mode's I/O maps it.
63
+ def bank_io_peek(addr)
64
+ lay_out_io(@bank_io_reads, @bank_io_writes)
65
+ @bank_io_reads[0xd5] = @mmu
66
+ @bank_io_reads[addr >> 8].peek(addr)
67
+ end
68
+
69
+ # Writes the chip at +addr+, in $D000-$DFFF, as C128 mode's I/O maps it.
70
+ def bank_io_poke(addr, value)
71
+ lay_out_io(@bank_io_reads, @bank_io_writes)
72
+ @bank_io_writes[0xd5] = @mmu
73
+ @bank_io_writes[addr >> 8].poke(addr, value)
74
+ end
75
+
49
76
  # The bank CR picks, with common RAM in bank 0, then the pages page 0
50
77
  # and page 1 move to.
51
78
  def map_ram_pages
@@ -46,15 +46,25 @@ module Badline
46
46
 
47
47
  # The keyboard buffer of the KERNAL the machine runs: the C64's, or in
48
48
  # C128 mode BASIC 7.0's.
49
- def feed_keyboard
50
- c64 = mode == :c64
51
- count = c64 ? KeyboardBuffer::COUNT : C128_KEYBOARD_COUNT
52
- return unless ram.peek(count).zero?
53
-
54
- chunk = @pending_keys.shift(KeyboardBuffer::CAPACITY)
55
- ram.write(c64 ? KeyboardBuffer::ADDRESS : C128_KEYBOARD_BUFFER, chunk)
56
- ram.poke(count, chunk.length)
57
- @pending_keys = nil if @pending_keys.empty?
49
+ def keyboard_buffer
50
+ mode == :c64 ? Machine::KeyboardBuffer::C64_LAYOUT : Machine::KeyboardBuffer::C128_LAYOUT
51
+ end
52
+
53
+ # Types the text type_text left: into the keyboard buffer, or while
54
+ # the Z80 runs, as under CP/M, where no KERNAL reads the buffer, on
55
+ # the key matrix, all of it once begun there.
56
+ def type_pending
57
+ if @z80_running || @typist.typing?
58
+ type_on_matrix
59
+ else
60
+ feed_keyboard
61
+ end
62
+ end
63
+
64
+ def type_on_matrix
65
+ pending = @pending_keys
66
+ @typist.cycle!(pending)
67
+ @pending_keys = nil if pending.empty? && !@typist.typing?
58
68
  end
59
69
  end
60
70
  end
@@ -58,7 +58,7 @@ module Badline
58
58
  # interrupt lines going to the Z80.
59
59
  def clock_chips
60
60
  handle_init if @cycles == @init_threshold
61
- feed_keyboard if @pending_keys
61
+ type_pending if @pending_keys
62
62
 
63
63
  clock_bits = @clock_bits
64
64
  @clock_bits = @vic.clock_bits
data/lib/badline/c128.rb CHANGED
@@ -8,12 +8,14 @@ require "badline/c128/io_pages"
8
8
  require "badline/c128/z80_pages"
9
9
  require "badline/c128/color_lines"
10
10
  require "badline/c128/banks"
11
- require "badline/c128/bus/saved_state"
11
+ require "badline/c128/bus/keyed_state"
12
12
  require "badline/c128/bus"
13
13
  require "badline/c128/cpu"
14
14
  require "badline/c128/z80"
15
15
  require "badline/c128/z80_turns"
16
- require "badline/c128/saved_state"
16
+ require "badline/snapshot/c128_setup"
17
+ require "badline/c128/keyed_state"
18
+ require "badline/c128/keyed_modules"
17
19
  require "badline/c128/keys"
18
20
  require "badline/c128/modes"
19
21
  require "badline/c128/drives"
@@ -43,8 +45,9 @@ module Badline
43
45
  # mirrors, and $D500-$D7FF holds the hidden MMU, the VDC and open bus.
44
46
  class C128
45
47
  include IntegerHelper
46
- include KeyboardBuffer
48
+ include Machine::KeyboardBuffer
47
49
  include Machine::Clocking
50
+ include Machine::Settling
48
51
  include C64::Common
49
52
  include C64::Attachments
50
53
  include C64::KernalTraps
@@ -67,10 +70,6 @@ module Badline
67
70
  # BASIC 7.0's, which is READY by then.
68
71
  C128_INIT_THRESHOLD = 2_000_000
69
72
 
70
- # The C128 KERNAL's keyboard buffer and its count.
71
- C128_KEYBOARD_BUFFER = 0x034a
72
- C128_KEYBOARD_COUNT = 0xd0
73
-
74
73
  attr_reader :cpu, :cycles, :drive1541, :model, :init_threshold
75
74
 
76
75
  def family = :c128
@@ -140,6 +139,7 @@ module Badline
140
139
  @restore_pulse = false
141
140
  @init_handlers = []
142
141
  @pending_keys = nil
142
+ @typist = Input::MatrixTypist.new(@bus.keyboard)
143
143
  @capture_output = nil
144
144
  init_drives
145
145
  enter_c64_mode if @c64_built
@@ -154,7 +154,7 @@ module Badline
154
154
  return z80_cycle! if @z80_turn
155
155
 
156
156
  handle_init if @cycles == @init_threshold
157
- feed_keyboard if @pending_keys
157
+ type_pending if @pending_keys
158
158
 
159
159
  clock_bits = @clock_bits
160
160
  @clock_bits = @vic.clock_bits
@@ -37,50 +37,6 @@ module Badline
37
37
  def push_serial_lines
38
38
  @iec_bus.host_lines = @cia2.port_a_lines
39
39
  end
40
-
41
- def save_cartridge(out)
42
- cartridge = address_bus.cartridge
43
- out.boolean(!cartridge.nil?)
44
- cartridge&.save_setup(out)
45
- end
46
-
47
- # A cartridge comes back built afresh from the setup it was built
48
- # with, before the address bus puts its state into it.
49
- def load_cartridge(input)
50
- if input.boolean?
51
- connect_cartridge(Cartridge.from_setup(input))
52
- elsif address_bus.cartridge
53
- address_bus.detach_cartridge
54
- end
55
- end
56
-
57
- # A true 1541 on the serial bus, then a 1581.
58
- def save_serial_bus(out)
59
- out.boolean(!@drive1541.nil?)
60
- if @drive1541
61
- out.int(@drive1541.device)
62
- @drive1541.save_state(out)
63
- end
64
- save_drive1581(out)
65
- end
66
-
67
- def load_serial_bus(input)
68
- load_drive1541(input)
69
- load_drive1581(input)
70
- end
71
-
72
- # The REU, which the setup says the machine has, and whether it has
73
- # asked for the bus. Its IRQ line reaches the CPU as it was.
74
- def save_reu(out)
75
- @reu&.save_state(out)
76
- out.boolean(@dma)
77
- end
78
-
79
- def load_reu(input)
80
- @reu&.load_state(input)
81
- @reu_irq = @reu ? @reu.irq? : false
82
- @dma = input.boolean?
83
- end
84
40
  end
85
41
  end
86
42
  end
@@ -33,18 +33,26 @@ module Badline
33
33
 
34
34
  def type = :plus256k
35
35
 
36
- def save_state(out)
37
- [@low_ram, @video_ram, @high_ram].each { |bank| out.int(@banks.index(bank)) }
38
- out.boolean(@locked)
39
- @banks.drop(1).each { |bank| bank.save_state(out) }
36
+ # $D100 as the banks it picks give it, whether it is locked, and
37
+ # banks 1 to 3, in a keyed snapshot module.
38
+ def save_fields(out)
39
+ low = @banks.index(@low_ram).to_i
40
+ video = @banks.index(@video_ram).to_i
41
+ out.int("d100", low | (video << 2) | (@banks.index(@high_ram).to_i << 6)).bool("locked", @locked)
42
+ @banks[1].save_fields(out, "bank1")
43
+ @banks[2].save_fields(out, "bank2")
44
+ @banks[3].save_fields(out, "bank3")
40
45
  end
41
46
 
42
- def load_state(input)
43
- @low_ram = @banks.fetch(input.int)
44
- @video_ram = @banks.fetch(input.int)
45
- @high_ram = @banks.fetch(input.int)
46
- @locked = input.boolean?
47
- @banks.drop(1).each { |bank| bank.load_state(input) }
47
+ def load_fields(input)
48
+ value = input.byte("d100", 0)
49
+ @low_ram = @banks[value & 0b11]
50
+ @video_ram = @banks[(value >> 2) & 0b11]
51
+ @high_ram = @banks[value >> 6]
52
+ @locked = input.bool("locked", @locked)
53
+ @banks[1].load_fields(input, "bank1")
54
+ @banks[2].load_fields(input, "bank2")
55
+ @banks[3].load_fields(input, "bank3")
48
56
  end
49
57
 
50
58
  def power_on!
@@ -26,14 +26,16 @@ module Badline
26
26
 
27
27
  def type = :plus60k
28
28
 
29
- def save_state(out)
30
- out.int(@banks.index(@high_ram))
31
- @banks[1].save_state(out)
29
+ # $D100 as bit 7 picks the bank above $1000, and bank 1, in a
30
+ # keyed snapshot module.
31
+ def save_fields(out)
32
+ out.int("d100", @banks.index(@high_ram).to_i << 7)
33
+ @banks[1].save_fields(out, "bank1")
32
34
  end
33
35
 
34
- def load_state(input)
35
- @high_ram = @banks.fetch(input.int)
36
- @banks[1].load_state(input)
36
+ def load_fields(input)
37
+ @high_ram = @banks[(input.byte("d100", @banks.index(@high_ram).to_i << 7) >> 7) & 1]
38
+ @banks[1].load_fields(input, "bank1")
37
39
  end
38
40
 
39
41
  def power_on!
@@ -17,9 +17,9 @@ module Badline
17
17
 
18
18
  def map_io(_read_pages, _write_pages); end
19
19
 
20
- def save_state(_out); end
20
+ def save_fields(_out); end
21
21
 
22
- def load_state(_input); end
22
+ def load_fields(_input); end
23
23
 
24
24
  def reset!; end
25
25
 
@@ -0,0 +1,32 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Badline
4
+ class C64
5
+ class Bus
6
+ # The VICE debug cartridge register, mapped at $D7FF on the C64. It is a
7
+ # VICE facility rather than real hardware, enabled there with -debugcart:
8
+ # writing a byte ends the run with that value as the exit code. The VICE
9
+ # test suites write $00 for success and $ff for failure.
10
+ #
11
+ # Wraps the SID's top mirror page, so every other access falls through.
12
+ class DebugRegister
13
+ ADDRESS = 0xd7ff
14
+
15
+ def initialize(sid, &handler)
16
+ @sid = sid
17
+ @handler = handler
18
+ end
19
+
20
+ def peek(addr)
21
+ @sid.peek(addr)
22
+ end
23
+
24
+ def poke(addr, value)
25
+ return @handler.call(value) if addr == ADDRESS
26
+
27
+ @sid.poke(addr, value)
28
+ end
29
+ end
30
+ end
31
+ end
32
+ end
@@ -7,7 +7,16 @@ module Badline
7
7
  # them, each over its 32 bytes of the I/O area (SIDSlots). The SID
8
8
  # player's machine has nothing else in I/O 1 and 2, so the rest of a
9
9
  # page there reads the open bus.
10
+ #
11
+ # A snapshot holds each in its own keyed module, BL-SID2 onwards in the
12
+ # order they were fitted, with the address it sits at and its model
13
+ # ahead of the SID's own fields.
10
14
  module ExtraSIDs
15
+ SID_MODEL_NAMES = %w[mos6581 mos8580].freeze
16
+
17
+ # The SIDs fitted past the first, in the order they were fitted.
18
+ attr_reader :extra_sids
19
+
11
20
  # Fits a SID at the address it was built at, where it takes its 32
12
21
  # bytes over from whatever maps there.
13
22
  def add_sid(sid)
@@ -18,14 +27,85 @@ module Badline
18
27
  @sid_slots << slots
19
28
  end
20
29
  slots.place(sid)
30
+ @extra_sids << sid
31
+ update_overlays!
32
+ end
33
+
34
+ # Takes the extra SIDs out, giving their addresses back to what maps
35
+ # there.
36
+ def remove_extra_sids
37
+ @sid_slots.clear
38
+ @extra_sids.clear
21
39
  update_overlays!
22
40
  end
23
41
 
42
+ def save_extra_sid_modules(sections)
43
+ @extra_sids.each_with_index do |sid, index|
44
+ sections << Snapshot::Keyed.section("BL-SID#{index + 2}") do |out|
45
+ out.int("address", sid.start).text("model", sid.model.to_s)
46
+ sid.save_fields(out)
47
+ end
48
+ end
49
+ sections
50
+ end
51
+
52
+ # Fits the extra SIDs the snapshot's modules hold and loads them. A
53
+ # SID already fitted at the same address stays, on the model the
54
+ # snapshot names, and one the snapshot hasn't comes out. A module
55
+ # whose address no SID fits at is left out, and reported.
56
+ def load_extra_sid_modules(modules)
57
+ addresses = []
58
+ inputs = extra_sid_inputs(modules, addresses)
59
+ models = inputs.map { |input| sid_model_named(input.choice("model", SID_MODEL_NAMES, "mos6581")) }
60
+ refit_sids(addresses, models)
61
+ @extra_sids.each_with_index do |sid, index|
62
+ model = models[index]
63
+ sid.model = model unless sid.model == model
64
+ sid.load_fields(inputs[index])
65
+ end
66
+ end
67
+
24
68
  private
25
69
 
26
70
  def map_extra_sids
27
71
  @sid_slots.each { |slots| @read_pages[slots.page] = @write_pages[slots.page] = slots }
28
72
  end
73
+
74
+ def extra_sid_inputs(modules, taken)
75
+ inputs = []
76
+ number = 2
77
+ input = modules.optional("BL-SID2")
78
+ while input
79
+ address = input.word("address", 0xd420)
80
+ if sid_fits?(address) && !taken.include?(address)
81
+ inputs << input
82
+ taken << address
83
+ else
84
+ input.adjust(format("no SID fits at $%04x, so it's left out", address))
85
+ end
86
+ number += 1
87
+ input = modules.optional("BL-SID#{number}")
88
+ end
89
+ inputs
90
+ end
91
+
92
+ # A SID's 32 bytes past the first SID's own, in $D420-$D7FF, or in
93
+ # I/O 1 or 2.
94
+ def sid_fits?(address)
95
+ address.nobits?(0x1f) && ((0xd420..0xd7e0).cover?(address) || (0xde00..0xdfe0).cover?(address))
96
+ end
97
+
98
+ def sid_model_named(name) = name == "mos8580" ? :mos8580 : :mos6581
99
+
100
+ def refit_sids(addresses, models)
101
+ fitted = @extra_sids.dup
102
+ return if fitted.map(&:start) == addresses
103
+
104
+ remove_extra_sids
105
+ addresses.each_with_index do |address, index|
106
+ add_sid(fitted.find { |sid| sid.start == address } || Chips::SID.new(model: models[index], at: address))
107
+ end
108
+ end
29
109
  end
30
110
  end
31
111
  end