badline 0.16.1 → 0.18.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 (256) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +213 -0
  3. data/CLAUDE.md +11 -7
  4. data/CONTRIBUTING.md +7 -1
  5. data/README.md +265 -26
  6. data/doc/media.md +5 -2
  7. data/doc/pinned-behaviour.md +189 -0
  8. data/doc/snapshot-format.md +616 -74
  9. data/doc/snapshots.md +192 -24
  10. data/exe/badline-ruby +5 -0
  11. data/lib/badline/audio/terminal.rb +34 -2
  12. data/lib/badline/c128/keyed_state.rb +2 -0
  13. data/lib/badline/c128.rb +1 -0
  14. data/lib/badline/c64/keyed_state.rb +2 -0
  15. data/lib/badline/c64.rb +1 -0
  16. data/lib/badline/cbm2/acia.rb +10 -0
  17. data/lib/badline/cbm2/bus.rb +263 -0
  18. data/lib/badline/cbm2/cpu.rb +58 -0
  19. data/lib/badline/cbm2/crtc_video.rb +133 -0
  20. data/lib/badline/cbm2/ieee_drive.rb +18 -0
  21. data/lib/badline/cbm2/ieee_layout.rb +69 -0
  22. data/lib/badline/cbm2/ieee_ports.rb +104 -0
  23. data/lib/badline/cbm2/ieee_traps.rb +81 -0
  24. data/lib/badline/cbm2/keyboard_ports.rb +60 -0
  25. data/lib/badline/cbm2/keyed_modules.rb +67 -0
  26. data/lib/badline/cbm2/keyed_state.rb +95 -0
  27. data/lib/badline/cbm2/model.rb +44 -0
  28. data/lib/badline/cbm2/p500/control_ports.rb +80 -0
  29. data/lib/badline/cbm2/p500/ieee_layout.rb +41 -0
  30. data/lib/badline/cbm2/p500/keyed_state.rb +85 -0
  31. data/lib/badline/cbm2/p500/model.rb +39 -0
  32. data/lib/badline/cbm2/p500/pages.rb +57 -0
  33. data/lib/badline/cbm2/p500.rb +264 -0
  34. data/lib/badline/cbm2/sid_page.rb +19 -0
  35. data/lib/badline/cbm2.rb +270 -0
  36. data/lib/badline/{plus4 → chips}/acia/frames.rb +1 -1
  37. data/lib/badline/chips/acia/keyed_state.rb +47 -0
  38. data/lib/badline/chips/acia.rb +307 -0
  39. data/lib/badline/chips/cia/timer.rb +2 -2
  40. data/lib/badline/chips/cpu/instruction_set.rb +1 -1
  41. data/lib/badline/chips/crtc.rb +3 -2
  42. data/lib/badline/chips/m6809/arithmetic.rb +140 -0
  43. data/lib/badline/chips/m6809/bus_steps.rb +223 -0
  44. data/lib/badline/chips/m6809/core.rb +149 -0
  45. data/lib/badline/chips/m6809/indexed.rb +82 -0
  46. data/lib/badline/chips/m6809/interrupts.rb +159 -0
  47. data/lib/badline/chips/m6809/keyed_state.rb +147 -0
  48. data/lib/badline/chips/m6809/opcodes.rb +159 -0
  49. data/lib/badline/chips/m6809/operations.rb +258 -0
  50. data/lib/badline/chips/m6809/plans.rb +143 -0
  51. data/lib/badline/chips/m6809/registers.rb +82 -0
  52. data/lib/badline/chips/m6809/stack.rb +140 -0
  53. data/lib/badline/chips/m6809.rb +13 -0
  54. data/lib/badline/chips/riot/fast_forward.rb +72 -0
  55. data/lib/badline/chips/riot/keyed_parts.rb +69 -0
  56. data/lib/badline/chips/riot/timer.rb +91 -0
  57. data/lib/badline/chips/riot.rb +162 -0
  58. data/lib/badline/chips/rriot/keyed_state.rb +30 -0
  59. data/lib/badline/chips/rriot.rb +134 -0
  60. data/lib/badline/chips/sid/filter.rb +4 -4
  61. data/lib/badline/chips/sid/waveform.rb +6 -6
  62. data/lib/badline/chips/tpi/keyed_state.rb +38 -0
  63. data/lib/badline/chips/tpi.rb +233 -0
  64. data/lib/badline/chips/vic/bank.rb +60 -5
  65. data/lib/badline/chips/vic/sprite.rb +4 -3
  66. data/lib/badline/chips/vic_i/keyed_parts.rb +7 -2
  67. data/lib/badline/chips/vic_i.rb +84 -21
  68. data/lib/badline/data_path.rb +3 -3
  69. data/lib/badline/drives/datasette/keyed_state.rb +9 -1
  70. data/lib/badline/drives/datasette.rb +27 -3
  71. data/lib/badline/drives/ieee_models.rb +54 -0
  72. data/lib/badline/drives/model1001.rb +37 -0
  73. data/lib/badline/drives/model2031.rb +6 -0
  74. data/lib/badline/drives/model4040/disk.rb +62 -0
  75. data/lib/badline/drives/model4040/ip_bus.rb +46 -0
  76. data/lib/badline/drives/model4040/keyed_state.rb +57 -0
  77. data/lib/badline/drives/model4040/mechanism.rb +192 -0
  78. data/lib/badline/drives/model4040/parts.rb +20 -0
  79. data/lib/badline/drives/model4040.rb +179 -0
  80. data/lib/badline/drives/model8050/board.rb +77 -0
  81. data/lib/badline/drives/model8050/disk.rb +163 -0
  82. data/lib/badline/drives/model8050/dos_board.rb +91 -0
  83. data/lib/badline/drives/model8050/fdc_bus.rb +102 -0
  84. data/lib/badline/drives/model8050/idle.rb +181 -0
  85. data/lib/badline/drives/model8050/ieee_port.rb +147 -0
  86. data/lib/badline/drives/model8050/ip_bus.rb +93 -0
  87. data/lib/badline/drives/model8050/keyed_state.rb +163 -0
  88. data/lib/badline/drives/model8050/mechanism.rb +230 -0
  89. data/lib/badline/drives/model8050/parts.rb +37 -0
  90. data/lib/badline/drives/model8050.rb +131 -0
  91. data/lib/badline/drives/model9060/idle.rb +149 -0
  92. data/lib/badline/drives/model9060/keyed_state.rb +123 -0
  93. data/lib/badline/drives/model9060/parts.rb +98 -0
  94. data/lib/badline/drives/model9060/sasi_bus.rb +88 -0
  95. data/lib/badline/drives/model9060/sasi_commands.rb +168 -0
  96. data/lib/badline/drives/model9060/sasi_target.rb +161 -0
  97. data/lib/badline/drives/model9060.rb +195 -0
  98. data/lib/badline/frontend/app.rb +18 -3
  99. data/lib/badline/frontend/boot.rb +4 -3
  100. data/lib/badline/frontend/controls.rb +107 -15
  101. data/lib/badline/frontend/gamepads.rb +3 -3
  102. data/lib/badline/frontend/keys.rb +97 -2
  103. data/lib/badline/frontend/kim1_panel.rb +208 -0
  104. data/lib/badline/frontend/media_slots.rb +8 -1
  105. data/lib/badline/frontend/menu/drive_types.rb +56 -0
  106. data/lib/badline/frontend/menu/menu_dialogs.rb +61 -14
  107. data/lib/badline/frontend/menu/menu_media.rb +47 -14
  108. data/lib/badline/frontend/menu/menu_pages.rb +113 -4
  109. data/lib/badline/frontend/menu/menu_ports.rb +77 -4
  110. data/lib/badline/frontend/menu/pause_menu.rb +6 -2
  111. data/lib/badline/frontend/pet_keyboard.rb +45 -7
  112. data/lib/badline/frontend/screen.rb +1 -1
  113. data/lib/badline/frontend/screens.rb +40 -2
  114. data/lib/badline/frontend/teletype_pane.rb +152 -0
  115. data/lib/badline/frontend/timeline.rb +49 -11
  116. data/lib/badline/frontend.rb +3 -0
  117. data/lib/badline/kernal_trap/dos/disk_commands.rb +80 -0
  118. data/lib/badline/kernal_trap/dos/status.rb +3 -0
  119. data/lib/badline/kernal_trap/dos.rb +18 -5
  120. data/lib/badline/kernal_trap/ieee.rb +4 -2
  121. data/lib/badline/kernal_trap/ieee_layout.rb +95 -43
  122. data/lib/badline/kim1/bus.rb +117 -0
  123. data/lib/badline/kim1/cpu.rb +25 -0
  124. data/lib/badline/kim1/host_terminal.rb +74 -0
  125. data/lib/badline/kim1/key_entry.rb +74 -0
  126. data/lib/badline/kim1/keyed_modules.rb +53 -0
  127. data/lib/badline/kim1/keyed_state.rb +133 -0
  128. data/lib/badline/kim1/panel.rb +176 -0
  129. data/lib/badline/kim1/rriots.rb +28 -0
  130. data/lib/badline/kim1/sound.rb +29 -0
  131. data/lib/badline/kim1/tape.rb +116 -0
  132. data/lib/badline/kim1/teletype.rb +193 -0
  133. data/lib/badline/kim1.rb +190 -0
  134. data/lib/badline/machine/ieee_drives.rb +171 -0
  135. data/lib/badline/machine/keyboard_buffer.rb +20 -3
  136. data/lib/badline/machine/line_sound.rb +122 -0
  137. data/lib/badline/machine/settling.rb +4 -0
  138. data/lib/badline/machine.rb +62 -14
  139. data/lib/badline/media/cbm2_media.rb +29 -0
  140. data/lib/badline/media/extensions.rb +8 -4
  141. data/lib/badline/media/kim1_media.rb +150 -0
  142. data/lib/badline/media/pet_media.rb +3 -1
  143. data/lib/badline/media/program.rb +6 -3
  144. data/lib/badline/media/true_drive.rb +51 -25
  145. data/lib/badline/media.rb +23 -8
  146. data/lib/badline/options/family.rb +108 -6
  147. data/lib/badline/options/help.rb +58 -12
  148. data/lib/badline/options/table.rb +60 -14
  149. data/lib/badline/options/validation.rb +71 -11
  150. data/lib/badline/options.rb +46 -13
  151. data/lib/badline/pet/bus.rb +16 -2
  152. data/lib/badline/pet/cassettes.rb +22 -1
  153. data/lib/badline/pet/ieee_drive.rb +5 -64
  154. data/lib/badline/pet/ieee_ports.rb +7 -0
  155. data/lib/badline/pet/keyboard_ports.rb +9 -2
  156. data/lib/badline/pet/keyed_state.rb +4 -0
  157. data/lib/badline/pet/model.rb +11 -2
  158. data/lib/badline/pet/rom_sets.rb +1 -0
  159. data/lib/badline/pet/sound.rb +5 -105
  160. data/lib/badline/pet/superpet/acia.rb +12 -0
  161. data/lib/badline/pet/superpet/board.rb +221 -0
  162. data/lib/badline/pet/superpet/dongle.rb +81 -0
  163. data/lib/badline/pet/superpet/keyed_modules.rb +63 -0
  164. data/lib/badline/pet/superpet/keyed_parts.rb +76 -0
  165. data/lib/badline/pet/superpet/m6809.rb +32 -0
  166. data/lib/badline/pet/superpet/waterloo_traps.rb +199 -0
  167. data/lib/badline/pet/superpet.rb +216 -0
  168. data/lib/badline/pet.rb +33 -6
  169. data/lib/badline/plus4/acia.rb +4 -298
  170. data/lib/badline/plus4/keyed_parts.rb +0 -44
  171. data/lib/badline/plus4/keyed_state.rb +2 -0
  172. data/lib/badline/plus4.rb +2 -0
  173. data/lib/badline/roms/1001/fdc-251257-02a.rom +0 -0
  174. data/lib/badline/roms/4040/dos-1.2.rom +0 -0
  175. data/lib/badline/roms/4040/dos-2.1-rev.rom +0 -0
  176. data/lib/badline/roms/4040/dos-2.1.rom +0 -0
  177. data/lib/badline/roms/4040/fdc-901466-02.rom +0 -0
  178. data/lib/badline/roms/4040/fdc-901466-04.rom +0 -0
  179. data/lib/badline/roms/8050/dos-2.7.rom +0 -0
  180. data/lib/badline/roms/8050/fdc-901869-01.rom +0 -0
  181. data/lib/badline/roms/8050/fdc-901885-04.rom +0 -0
  182. data/lib/badline/roms/9060/dos-3.0.rom +0 -0
  183. data/lib/badline/roms/9060/sasi-300515-revb.rom +0 -0
  184. data/lib/badline/roms/README +277 -0
  185. data/lib/badline/roms/cbm2/basic128-02b.rom +0 -0
  186. data/lib/badline/roms/cbm2/basic128.rom +0 -0
  187. data/lib/badline/roms/cbm2/basic256.rom +0 -0
  188. data/lib/badline/roms/cbm2/characters-600.rom +0 -0
  189. data/lib/badline/roms/cbm2/characters-700.rom +0 -0
  190. data/lib/badline/roms/cbm2/kernal-03b.rom +0 -0
  191. data/lib/badline/roms/cbm2/kernal-04a.rom +0 -0
  192. data/lib/badline/roms/cbm2/p500-basic.rom +0 -0
  193. data/lib/badline/roms/cbm2/p500-kernal.rom +0 -0
  194. data/lib/badline/roms/kim1/6530-002.rom +0 -0
  195. data/lib/badline/roms/kim1/6530-003.rom +0 -0
  196. data/lib/badline/roms/pet/characters-superpet.rom +0 -0
  197. data/lib/badline/roms/pet/superpet-a000.rom +0 -0
  198. data/lib/badline/roms/pet/superpet-c000.rom +0 -0
  199. data/lib/badline/roms/pet/superpet-e000-50hz.rom +0 -0
  200. data/lib/badline/roms/pet/superpet-e000.rom +0 -0
  201. data/lib/badline/snapshot/cbm2_setup.rb +53 -0
  202. data/lib/badline/snapshot/container.rb +9 -2
  203. data/lib/badline/snapshot/field_doc/markdown.rb +4 -2
  204. data/lib/badline/snapshot/field_doc.rb +59 -3
  205. data/lib/badline/snapshot/image.rb +65 -9
  206. data/lib/badline/snapshot/kim1_setup.rb +28 -0
  207. data/lib/badline/snapshot/pet_setup.rb +27 -11
  208. data/lib/badline/snapshot/storage_setup.rb +4 -1
  209. data/lib/badline/snapshot/vic20_setup.rb +11 -7
  210. data/lib/badline/snapshot/vice/cbm2.rb +93 -0
  211. data/lib/badline/snapshot/vice/cbm2_acia.rb +75 -0
  212. data/lib/badline/snapshot/vice/cbm2_crtc.rb +127 -0
  213. data/lib/badline/snapshot/vice/cbm2_mains.rb +49 -0
  214. data/lib/badline/snapshot/vice/cbm2_mem.rb +75 -0
  215. data/lib/badline/snapshot/vice/cbm2_restore.rb +204 -0
  216. data/lib/badline/snapshot/vice/cbm2_tpi.rb +58 -0
  217. data/lib/badline/snapshot/vice/cias.rb +13 -9
  218. data/lib/badline/snapshot/vice/p500.rb +96 -0
  219. data/lib/badline/snapshot/vice/p500_restore.rb +130 -0
  220. data/lib/badline/snapshot/vice/p500_vicii.rb +166 -0
  221. data/lib/badline/snapshot/vice/pet.rb +20 -10
  222. data/lib/badline/snapshot/vice/pet_cpu.rb +42 -5
  223. data/lib/badline/snapshot/vice/pet_crtc.rb +25 -17
  224. data/lib/badline/snapshot/vice/pet_drive.rb +180 -0
  225. data/lib/badline/snapshot/vice/pet_drive8050.rb +336 -0
  226. data/lib/badline/snapshot/vice/pet_mem.rb +49 -5
  227. data/lib/badline/snapshot/vice/pet_restore.rb +100 -7
  228. data/lib/badline/snapshot/vice/pet_via.rb +6 -5
  229. data/lib/badline/snapshot/vice/sid_registers.rb +2 -1
  230. data/lib/badline/snapshot/vice/vicii_import.rb +10 -6
  231. data/lib/badline/snapshot/vice.rb +3 -0
  232. data/lib/badline/snapshot.rb +15 -3
  233. data/lib/badline/status.rb +30 -30
  234. data/lib/badline/storage/crt_file.rb +7 -1
  235. data/lib/badline/storage/d64_image.rb +7 -0
  236. data/lib/badline/storage/d67_image.rb +55 -0
  237. data/lib/badline/storage/d80_image.rb +17 -1
  238. data/lib/badline/storage/d81_image.rb +14 -0
  239. data/lib/badline/storage/d90_image.rb +180 -0
  240. data/lib/badline/storage/disk_image/bam.rb +18 -3
  241. data/lib/badline/storage/disk_image/formatting.rb +69 -0
  242. data/lib/badline/storage/disk_image/writing.rb +37 -2
  243. data/lib/badline/storage/disk_image.rb +2 -0
  244. data/lib/badline/storage/kim_tape.rb +254 -0
  245. data/lib/badline/storage/papertape.rb +90 -0
  246. data/lib/badline/storage.rb +5 -0
  247. data/lib/badline/support/cbm2_checkpoint.rb +48 -0
  248. data/lib/badline/support/kim1_checkpoint.rb +41 -0
  249. data/lib/badline/support/p500_checkpoint.rb +35 -0
  250. data/lib/badline/support/superpet_checkpoint.rb +35 -0
  251. data/lib/badline/version.rb +1 -1
  252. data/lib/badline/vic20/keyed_modules.rb +6 -3
  253. data/lib/badline/vic20/keyed_state.rb +6 -4
  254. data/lib/badline/vic20.rb +26 -12
  255. data/lib/badline.rb +2 -0
  256. metadata +148 -2
@@ -0,0 +1,336 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Badline
4
+ module Snapshot
5
+ module Vice
6
+ module PET
7
+ # A true unit with an interface processor on device 8, as xpet 3.10,
8
+ # xcbm2 and xcbm5x0 save it with true drive emulation: an 8050, an
9
+ # 8250, an SFD-1001, a 4040, a 3040, a 2040, or a D9060 or D9090,
10
+ # which xpet's type 9000 stands for. Each is DRIVE8 2.0 with the
11
+ # unit's drives, DRIVE9 to DRIVE11 with none, DRIVECPU0 1.3, the
12
+ # interface processor and its RAM, RIOT1D0 and RIOT2D0 0.0, its two
13
+ # 6532s, and FDC0 0.0. xpet runs the floppy controller and the hard
14
+ # disk's SASI host at a high level, so their processors, VIAs and
15
+ # 6530s aren't in the snapshot: FDC0 holds the controller's state,
16
+ # its last track in byte 6.
17
+ #
18
+ # DRIVE8 holds a block of 139 bytes for each of the unit's drives,
19
+ # then 6 for the unit: each block has its head's half track, twice
20
+ # the track, at $10 and the unit's type as a word at $28 (TYPES).
21
+ # The 1001 and the hard disks have one block, and a hard disk's head
22
+ # rests at HARD_DISK_TRACK. DRIVECPU0 is laid out as the 2031's
23
+ # (Drive2031), its RAM the IP's page 0, the 6532s' 128 bytes each,
24
+ # then the 4K of common RAM. A RIOT module holds port A's output and
25
+ # direction registers, port B's, the edge control (bit 0 the PA7
26
+ # interrupt, bit 1 a rising edge), the flags (bit 7 the timer's
27
+ # interrupt, which xpet raises IRQ for, bit 6 PA7's), the timer's
28
+ # counter, its divider as a word, two bytes xpet leaves 0, and
29
+ # whether the timer interrupts.
30
+ #
31
+ # Written, once the controller, or the SASI host, is in its idle
32
+ # loop between instructions (PET#settle_for_vice!): the IP's
33
+ # registers and RAM, the 6532s, the heads' tracks, and FDC0 as xpet
34
+ # leaves an idle controller with the track badline's controller last
35
+ # stepped to. A unit whose controller is still busy is left out of
36
+ # xpet's modules. Read: the IP's registers and RAM and the 6532s,
37
+ # with the controller as a unit switched on has it in its idle loop,
38
+ # its heads on track 1, from where its next job steps them. A job
39
+ # under way in xpet's controller starts again from there. The disks
40
+ # in the drives stay, and a hard disk's image with them, which also
41
+ # says whether the unit is a D9060 or a D9090.
42
+ module Drive8050
43
+ UNIT = "DRIVE8"
44
+ CPU = Drive2031::CPU
45
+ RIOTS = %w[RIOT1D0 RIOT2D0].freeze
46
+ FDC = "FDC0"
47
+ NAMES = [UNIT, CPU, *RIOTS, FDC].freeze
48
+ # xpet's drive types by DRIVE8's type word, for the units written
49
+ # and read here.
50
+ TYPES = { 0x1f72 => "8050", 0x203a => "8250", 0x03e9 => "1001", 0x0fc8 => "4040", 0x0be0 => "3040",
51
+ 0x07f8 => "2040", 0x2328 => "9060" }.freeze
52
+ # The type word each model is written with.
53
+ WORDS = TYPES.invert.merge("9090" => 0x2328).freeze
54
+ # The units that are hard disks, and the DOS 2.1 and DOS 1 units
55
+ # on the 4040's board.
56
+ HARD_DISKS = %w[9060 9090].freeze
57
+ DOS2_BOARD = %w[4040 3040 2040].freeze
58
+ # The drives each unit has, a block of DRIVE8 each.
59
+ DRIVE_COUNTS = { "8050" => 2, "8250" => 2, "1001" => 1, "4040" => 2, "3040" => 2, "2040" => 2, "9060" => 1,
60
+ "9090" => 1 }.freeze
61
+ # A drive's block in DRIVE8, and its fields.
62
+ BLOCK = 0x8b
63
+ HALF_TRACK = 0x10
64
+ TYPE = 0x28
65
+ # The half track xpet keeps a hard disk's head on.
66
+ HARD_DISK_TRACK = 0x98
67
+ # The unit's bytes after the drives' blocks.
68
+ TAIL = 6
69
+ # DRIVE8 as xpet saves an idle 8050, by the bytes that aren't 0,
70
+ # but for the type and the drive count at COUNT: the first drive's
71
+ # block, and from BLOCK on the second's with a byte past it.
72
+ IDLE = { 0x00 => 0x01, 0x02 => 0x01, 0x0e => 0x01, 0x0f => 0x01, 0x10 => 0x4c, 0x1a => 0x01, 0x20 => 0x55,
73
+ 0x30 => 0x06, 0x6f => 0xcd, 0x70 => 0xab, 0x71 => 0x34, 0x72 => 0x12 }.freeze
74
+ IDLE_SECOND = { 0x0e => 0x01, 0x0f => 0x01, 0x10 => 0x24, 0x1a => 0x01, 0x20 => 0x55, 0x8f => 0x01 }.freeze
75
+ COUNT = 0x01
76
+ ZERO_PAGE = 0x80
77
+ COMMON = 0x1000
78
+ # FDC0 as xpet leaves an idle controller, and where the track goes.
79
+ FDC_IDLE = [0x04, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00].freeze
80
+ FDC_TRACK = 6
81
+ # Where badline's controller keeps drive 0's track, in the 6530's
82
+ # RAM: the 8050's, and the 4040's in the low six bits.
83
+ TRACK_AT = 0x21
84
+ DOS2_TRACK_AT = 0x03
85
+ DOS2_TRACK = 0x3f
86
+ DIVIDERS = [1, 8, 64, 1024].freeze
87
+ IRQ_EDGE = 0x01
88
+ RISING = 0x02
89
+ TIMER_FLAG = 0x80
90
+ PA7_FLAG = 0x40
91
+ # The hard disk's controller, which holds the image's path.
92
+ SASI = "BL-D8-SASI"
93
+
94
+ module_function
95
+
96
+ # Whether the snapshot has a true unit of these.
97
+ def present?(container) = !container[FDC].nil? && !container[CPU].nil?
98
+
99
+ def reads?(section)
100
+ case section.name
101
+ when CPU then Drive2031.reads?(section)
102
+ when UNIT then section.major == Drive2031::DRIVE_MAJOR && section.minor == Drive2031::DRIVE_MINOR
103
+ else section.major.zero? && section.minor.zero?
104
+ end
105
+ end
106
+
107
+ # The model DRIVE8 names, or nil. Type 9000 reads as the D9060.
108
+ def model(container)
109
+ unit = container[UNIT]
110
+ return nil if unit.nil? || unit.data.bytesize < BLOCK
111
+
112
+ model = TYPES[FieldReader.new(unit).skip(TYPE).word]
113
+ model if model && unit.data.bytesize >= size(model)
114
+ end
115
+
116
+ # Whether xpet saves the two models as one type.
117
+ def same_type?(model, other) = WORDS.key?(model) && WORDS[model] == WORDS[other]
118
+
119
+ def hard_disk?(model) = HARD_DISKS.include?(model)
120
+
121
+ # The hard disk whose image the kept modules `disks` hold: a
122
+ # D9090 for one with six heads, and otherwise a D9060.
123
+ def hard_disk_model(disks)
124
+ own = disks.find { |section| section.name == SASI }
125
+ own && Keyed::Reader.parse(own).int("disk_heads", 4) == 6 ? "9090" : "9060"
126
+ end
127
+
128
+ # DRIVE8's size for the model.
129
+ def size(model) = (BLOCK * DRIVE_COUNTS.fetch(model)) + TAIL
130
+
131
+ # xpet's modules for the unit whose BL-D8 modules `readers` holds,
132
+ # and none without one, or with its controller away from its idle
133
+ # loop.
134
+ def export(machine, readers)
135
+ drive = readers["BL-D8"]
136
+ model = drive&.text("model", "")
137
+ return [] unless WORDS.key?(model) && idle?(model, readers)
138
+
139
+ [unit(model, readers), *Drive2031::UNITS.drop(1).map { |name| none(name) },
140
+ Drive2031.cpu_section(machine, readers.fetch("BL-D8-IPCPU"), ram(readers)),
141
+ riot(RIOTS[0], readers.fetch("BL-D8-RIOT1")), riot(RIOTS[1], readers.fetch("BL-D8-RIOT2")),
142
+ fdc(model, readers)]
143
+ end
144
+
145
+ # Whether both processors are between instructions, the
146
+ # controller at the start of its idle loop.
147
+ def idle?(model, readers)
148
+ controller = readers.fetch(hard_disk?(model) ? "BL-D8-SASICPU" : "BL-D8-FDCCPU")
149
+ !position?(controller) && !position?(readers.fetch("BL-D8-IPCPU")) &&
150
+ controller.int("pc", 0) == controller_loop(model)
151
+ end
152
+
153
+ # Where the model's controller, or its SASI host, waits for jobs.
154
+ def controller_loop(model)
155
+ case model
156
+ when "9060", "9090" then Drives::Model9060::Idle::SASI_LOOP
157
+ when "2040" then Drives::Model2040::FDC_LOOP
158
+ when "4040", "3040" then Drives::Model4040::FDC_LOOP
159
+ else Drives::Model8050::Idle::FDC_LOOP
160
+ end
161
+ end
162
+
163
+ def position?(cpu) = !cpu.text("pos.seq", "").empty?
164
+
165
+ def unit(model, readers)
166
+ count = DRIVE_COUNTS.fetch(model)
167
+ bytes = Array.new(size(model), 0)
168
+ IDLE.each { |offset, value| bytes[offset] = value }
169
+ IDLE_SECOND.each { |offset, value| bytes[BLOCK + offset] = value } if count > 1
170
+ bytes[COUNT] = count
171
+ tracks = half_tracks(model, readers)
172
+ count.times do |drive|
173
+ at = BLOCK * drive
174
+ bytes[at + HALF_TRACK] = tracks[drive]
175
+ bytes[at + TYPE, 2] = [WORDS.fetch(model)].pack("v").bytes
176
+ end
177
+ FieldWriter.new.bytes(bytes).section(UNIT, Drive2031::DRIVE_MAJOR, Drive2031::DRIVE_MINOR)
178
+ end
179
+
180
+ # Each drive's head as xpet's half track: the 8050's cylinder on
181
+ # from 1, the 4040's half track on from track 1, and a hard
182
+ # disk's where xpet keeps it.
183
+ def half_tracks(model, readers)
184
+ return [HARD_DISK_TRACK] if hard_disk?(model)
185
+
186
+ mechanism = readers.fetch("BL-D8-MECH")
187
+ if DOS2_BOARD.include?(model)
188
+ mechanism.ints("half_tracks", [0, 0]).map { |half_track| half_track + 2 }
189
+ else
190
+ mechanism.ints("cylinders", [0, 0]).map { |cylinder| (cylinder + 1) * 2 }
191
+ end
192
+ end
193
+
194
+ def none(name) = Drive2031.unit(Drive2031::NONE, name)
195
+
196
+ # The IP's RAM as xpet lays it out: page 0 from both 6532s, then
197
+ # the common RAM.
198
+ def ram(readers)
199
+ [readers.fetch("BL-D8-RIOT1"), readers.fetch("BL-D8-RIOT2")].flat_map do |riot|
200
+ filled(riot.bytes("ram", "".b).bytes, ZERO_PAGE)
201
+ end + filled(readers.fetch("BL-D8-COMMON").bytes("ram", "".b).bytes, COMMON)
202
+ end
203
+
204
+ def filled(bytes, length) = Array.new(length) { |index| bytes[index] || 0 }
205
+
206
+ def riot(name, input)
207
+ edge = input.bool("edge_irq_enabled", false) ? IRQ_EDGE : 0
208
+ edge |= RISING if input.bool("rising_edge", false)
209
+ timer = input.bool("timer.flag", false) && input.bool("timer.irq_enabled", false)
210
+ flags = (timer ? TIMER_FLAG : 0) | (input.bool("pa7_flag", false) ? PA7_FLAG : 0)
211
+ divider = DIVIDERS.fetch(input.int("timer.interval", 0) & 0x03)
212
+ fields = FieldWriter.new.byte(input.int("ora", 0)).byte(input.int("ddra", 0)).byte(input.int("orb", 0))
213
+ fields.byte(input.int("ddrb", 0)).byte(edge).byte(flags).byte(input.int("timer.counter", 0)).word(divider)
214
+ fields.zeros(2).flag(input.bool("timer.irq_enabled", false)).section(name, 0, 0)
215
+ end
216
+
217
+ # FDC0 with the controller's track, and a hard disk's with none.
218
+ def fdc(model, readers)
219
+ bytes = FDC_IDLE.dup
220
+ unless hard_disk?(model)
221
+ ram = readers.fetch("BL-D8-RRIOT").bytes("ram", "".b)
222
+ bytes[FDC_TRACK] = if DOS2_BOARD.include?(model)
223
+ ram.getbyte(DOS2_TRACK_AT).to_i & DOS2_TRACK
224
+ else
225
+ ram.getbyte(TRACK_AT).to_i
226
+ end
227
+ end
228
+ FieldWriter.new.bytes(bytes).section(FDC, 0, 0)
229
+ end
230
+
231
+ # The unit's keyed modules, named for device 8, from the
232
+ # snapshot's: the IP's side from xpet's, the controller's side
233
+ # from `reference`, a unit's in its idle loop, and `disks`, the
234
+ # drives' disk modules badline keeps, or a hard disk's controller
235
+ # with its image.
236
+ def import(container, reference, disks)
237
+ cpu = container[CPU]
238
+ page = FieldReader.new(cpu).skip(Drive2031::RAM_AT).string(ZERO_PAGE * 2)
239
+ common = FieldReader.new(cpu).skip(Drive2031::RAM_AT + (ZERO_PAGE * 2)).string(COMMON)
240
+ sections = [Keyed.section("BL-D8") { |out| unit_fields(container, reference, out) },
241
+ Keyed.section("BL-D8-COMMON") { |out| out.bytes("ram", common) },
242
+ riot_section("BL-D8-RIOT1", container[RIOTS[0]], page.byteslice(0, ZERO_PAGE)),
243
+ riot_section("BL-D8-RIOT2", container[RIOTS[1]], page.byteslice(ZERO_PAGE, ZERO_PAGE)),
244
+ Keyed.section("BL-D8-IPCPU") { |out| Drive2031.registers(cpu, out) }]
245
+ sections + controller_side(reference, disks)
246
+ end
247
+
248
+ # The controller's side, which comes from a unit in its idle loop:
249
+ # a floppy unit's, and a hard disk's SASI host.
250
+ CONTROLLER = %w[BL-D8-FDCCPU BL-D8-VIA BL-D8-RRIOT BL-D8-MECH].freeze
251
+ HOST = %w[BL-D8-SASICPU BL-D8-SASIRAM BL-D8-VIA].freeze
252
+
253
+ def controller_side(reference, disks)
254
+ base = reference.find { |section| section.name == SASI }
255
+ return reference.select { |section| CONTROLLER.include?(section.name) } + disks if base.nil?
256
+
257
+ reference.select { |section| HOST.include?(section.name) } + [controller(base, disks)]
258
+ end
259
+
260
+ # The hard disk's controller as `base` has it, with the image
261
+ # badline's kept in `disks`.
262
+ def controller(base, disks)
263
+ kept = disks.find { |section| section.name == SASI }
264
+ return base if kept.nil?
265
+
266
+ target = Drives::Model9060::SASITarget.new
267
+ target.load_fields(Keyed::Reader.parse(base))
268
+ own = Keyed::Reader.parse(kept)
269
+ path = own.text("path", "")
270
+ if File.file?(path)
271
+ target.disk = Storage::D90Image.new(path, read_only: own.bool("read_only", false),
272
+ heads: own.int("disk_heads", 4))
273
+ end
274
+ Keyed.section(SASI) { |out| target.save_fields(out) }
275
+ end
276
+
277
+ # The unit's own fields: its model and clock, with the controller's
278
+ # last byte from `reference`, or a hard disk's SASI lines.
279
+ def unit_fields(container, reference, out)
280
+ own = Keyed::Reader.parse(reference.find { |section| section.name == "BL-D8" })
281
+ model = own.text("model", "8050")
282
+ out.text("model", model).int("phase", 0).int("cycles", FieldReader.new(container[CPU]).qword)
283
+ return out.int("fdc_data", own.int("fdc_data", 0)) unless hard_disk?(model)
284
+
285
+ out.int("sasi_data", own.int("sasi_data", 0)).bool("sasi.ack_high", own.bool("sasi.ack_high", true))
286
+ out.bool("sasi.reset_high", own.bool("sasi.reset_high", false))
287
+ end
288
+
289
+ def riot_section(name, section, ram)
290
+ fields = FieldReader.new(section)
291
+ ora, ddra, orb, ddrb, edge, flags, counter = fields.bytes(7)
292
+ divider = fields.word
293
+ fields.skip(2)
294
+ irq = fields.flag?
295
+ Keyed.section(name) do |out|
296
+ out.bytes("ram", ram).int("ora", ora).int("ddra", ddra).int("orb", orb).int("ddrb", ddrb)
297
+ out.bool("edge_irq_enabled", edge.anybits?(IRQ_EDGE)).bool("rising_edge", edge.anybits?(RISING))
298
+ out.bool("pa7_flag", flags.anybits?(PA7_FLAG)).int("timer.counter", counter)
299
+ out.int("timer.interval", DIVIDERS.index(divider) || 0).int("timer.prescale", [divider, 1].max)
300
+ out.bool("timer.flag", flags.anybits?(TIMER_FLAG)).bool("timer.expired", flags.anybits?(TIMER_FLAG))
301
+ out.bool("timer.irq_enabled", irq)
302
+ end
303
+ end
304
+
305
+ # A unit of `model` switched on and run into its controller's idle
306
+ # loop, both processors between instructions, as keyed modules
307
+ # named for device 8, run once a model.
308
+ def reference(model)
309
+ @references ||= {}
310
+ @references[model] ||= booted(model)
311
+ end
312
+
313
+ def booted(model)
314
+ unit = new_unit(model)
315
+ unit.boot_cycles.times { unit.host_cycle! }
316
+ unit.cycle! until unit.settled_for_vice?
317
+ unit.save_modules([], "BL-D8")
318
+ end
319
+
320
+ def new_unit(model)
321
+ case model
322
+ when "8250" then Drives::Model8250.new
323
+ when "1001" then Drives::Model1001.new
324
+ when "4040" then Drives::Model4040.new
325
+ when "3040" then Drives::Model3040.new
326
+ when "2040" then Drives::Model2040.new
327
+ when "9060" then Drives::Model9060.new
328
+ when "9090" then Drives::Model9090.new
329
+ else Drives::Model8050.new
330
+ end
331
+ end
332
+ end
333
+ end
334
+ end
335
+ end
336
+ end
@@ -5,14 +5,24 @@ module Badline
5
5
  module Vice
6
6
  module PET
7
7
  # PETMEM 1.3: the board, the RAM below $8000 in K and the 8096's
8
- # control register at $FFF0, then the RAM, the screen RAM, 1K or 2K
9
- # as the board has, or on the 8296 its 32K from $8000, an 8096's or
10
- # 8296's 64K board, and 24 bytes of xpet's own settings, which name
11
- # the 2001.
8
+ # control register at $FFF0, a byte for the SuperPET's latches, then
9
+ # the RAM, the screen RAM, 1K or 2K as the board has, or on the 8296
10
+ # its 32K from $8000, an 8096's, 8296's or SuperPET's 64K board, and
11
+ # 24 bytes of xpet's own settings, which name the 2001, and on the
12
+ # SuperPET the CPU that has the bus.
12
13
  #
13
14
  # The board is 0 for the discrete video, 1 for a 40-column CRTC, 2
14
15
  # for an 80-column one, 3 for the SuperPET, 4 for the 8096 and 5 for
15
16
  # the 8296. xpet's ROMs aren't in the snapshot.
17
+ #
18
+ # The SuperPET's latch byte holds the bank at $9000 in its high
19
+ # nibble, and sets bit 0 while the bank RAM is there rather than the
20
+ # empty socket, and bit 2 always. Its settings hold 1 at offset 2
21
+ # while the 6809 has the bus. Both were inferred from xpet 3.10's
22
+ # snapshots after writes to the bank latch and $EFFE, and in both
23
+ # modes. xpet has no system latch, so the bank latch's bit 7 and the
24
+ # system latch aren't saved, and a restore clears them. Nor is the
25
+ # 6702: a restore leaves it as the RES line does.
16
26
  module PETMem
17
27
  NAME = "PETMEM"
18
28
  MAJOR = 1
@@ -30,6 +40,14 @@ module Badline
30
40
  SETTINGS = [0x00, 0x00, 0x00, 0x00, 0xd6, 0x01, 0x00, 0x20, 0x00, 0x04, 0x00, 0x40, 0x00, 0x00, 0x00,
31
41
  0x01, 0x00, 0x00, 0x00, 0x10, 0x00, 0x02, 0x00, 0x00].freeze
32
42
  SETTINGS_2001 = 0x03
43
+ # The SuperPET's settings byte for the CPU, and its value while the
44
+ # 6809 has the bus.
45
+ SETTINGS_CPU = 2
46
+ SETTINGS_M6809 = 0x01
47
+ # The SuperPET's latch byte: the bit always set, and the one for
48
+ # the bank RAM at $9000.
49
+ LATCHES = 0x04
50
+ BANK_RAM = 0x01
33
51
  EXPANSION = 0x10000
34
52
 
35
53
  module_function
@@ -37,6 +55,7 @@ module Badline
37
55
  def reads?(section) = section.major == MAJOR && section.minor == MINOR
38
56
 
39
57
  def board(model)
58
+ return SUPERPET if model.superpet?
40
59
  return CBM8296 if model.cbm8296?
41
60
  return CBM8096 if model.expansion
42
61
 
@@ -50,15 +69,40 @@ module Badline
50
69
  def export(machine)
51
70
  bus = machine.bus
52
71
  expansion = bus.expansion
72
+ superpet = machine.superpet?
53
73
  fields = FieldWriter.new.byte(board(machine.model)).byte(0).byte(machine.ram_kb)
54
- fields.byte(expansion ? expansion.control : 0).byte(HEADER_END)
74
+ fields.byte(expansion ? expansion.control : 0).byte(superpet ? latches(machine.board) : HEADER_END)
55
75
  fields.bytes(machine.ram.snapshot).bytes(bus.screen.cells)
56
76
  fields.bytes(expansion.cells) if expansion
77
+ fields.bytes(machine.board.cells) if superpet
57
78
  settings = SETTINGS.dup
58
79
  settings[1] = SETTINGS_2001 if machine.model.basic1?
80
+ settings[SETTINGS_CPU] = SETTINGS_M6809 if superpet && machine.m6809_running?
59
81
  fields.bytes(settings).section(NAME, MAJOR, MINOR)
60
82
  end
61
83
 
84
+ def latches(board) = (board.bank << 4) | LATCHES | (board.socket.zero? ? BANK_RAM : 0)
85
+
86
+ # Whether a SuperPET's settings say the 6809 has the bus.
87
+ def m6809?(section)
88
+ data = section.data
89
+ offset = data.bytesize - SETTINGS.length + SETTINGS_CPU
90
+ offset >= 0 && data.getbyte(offset).to_i.anybits?(SETTINGS_M6809)
91
+ end
92
+
93
+ # A SuperPET's bank latch, socket bit and 64K board by their
94
+ # BL-SUPERPET keys, in a keyed module's writer.
95
+ def import_superpet(section, out)
96
+ fields = FieldReader.new(section).skip(4)
97
+ latches = fields.byte
98
+ fields.skip(fields_before_board(section))
99
+ out.int("bank_latch", latches >> 4).int("socket", latches.anybits?(BANK_RAM) ? 0 : 1)
100
+ out.int("system_latch", 0).bytes("ram", fields.string(EXPANSION))
101
+ end
102
+
103
+ # The RAM and the screen RAM before a SuperPET's board.
104
+ def fields_before_board(section) = (ram_kb(section) * 1024) + 0x800
105
+
62
106
  # The board's number in the header.
63
107
  def board_of(section) = FieldReader.new(section).byte
64
108
 
@@ -8,14 +8,23 @@ module Badline
8
8
  # reads become the PET's own keyed modules (Keyed), which it loads
9
9
  # as it loads its own snapshot. Whatever they don't set stays as the
10
10
  # machine has it at power-on, and the datasettes' tapes and the
11
- # disk device 8 serves stay as they are.
11
+ # disk device 8 serves stay as they are. A snapshot with a true drive
12
+ # puts one on the bus, with the disks badline's drive of that model
13
+ # had in it (Drive2031, Drive8050), and one without takes badline's
14
+ # off.
12
15
  module Restore
13
16
  # MAINCPU and PETMEM hold the machine itself, so a version badline
14
17
  # doesn't read fails the restore. The others are left out and
15
18
  # reported.
16
19
  READ = [MainCPU::NAME, PETMem::NAME, CRTC::NAME, "PIA1", "PIA2", VIA::NAME].freeze
20
+ # The SuperPET's too: its 6809 and its ACIA. A SuperPET's board
21
+ # comes from PETMEM.
22
+ SUPERPET_READ = [CPU6809::NAME, CBM2::ACIA::NAME].freeze
17
23
  # The host's media, which a restore keeps.
18
24
  KEPT = %w[BL-TAPE1 BL-TAPE2 BL-TRAPDOS].freeze
25
+ # The disks in a true drive's drives, and a hard disk's controller
26
+ # with its image, which a restore keeps.
27
+ DISKS = %w[BL-D8-DISK BL-D8-DISK1 BL-D8-SASI].freeze
19
28
 
20
29
  module_function
21
30
 
@@ -33,9 +42,13 @@ module Badline
33
42
  raise FormatError, "the snapshot is of a #{setup}, not a #{ours}" unless setup == ours
34
43
 
35
44
  apply(container, setup.build)
45
+ had_drive = computer.true_drive
36
46
  computer.power_cycle!
37
47
  report = apply(container, computer)
38
48
  kept = computer.mounted? ? ["the disk mounted in device 8 stays mounted"] : []
49
+ if had_drive && !computer.true_drive.equal?(had_drive)
50
+ kept << "the #{had_drive.model_name} on device #{had_drive.device} is taken off the bus"
51
+ end
39
52
  Report.new(applied: report.applied, ignored: report.ignored + kept)
40
53
  end
41
54
 
@@ -45,7 +58,8 @@ module Badline
45
58
  applied = []
46
59
  ignored = []
47
60
  sections = []
48
- READ.each do |name|
61
+ names = read_names(computer)
62
+ names.each do |name|
49
63
  section = container[name]
50
64
  next if section.nil?
51
65
 
@@ -57,9 +71,69 @@ module Badline
57
71
  ignored << "#{section}: badline doesn't read this version, left out"
58
72
  end
59
73
  end
60
- computer.save_modules.each { |section| sections << section if KEPT.include?(section.name) }
74
+ disks = []
75
+ computer.save_modules.each do |section|
76
+ sections << section if KEPT.include?(section.name)
77
+ disks << section if DISKS.include?(section.name)
78
+ end
79
+ sections += drive(container, applied, ignored, kept_disks(container, computer, disks))
61
80
  computer.load_modules(Keyed::Modules.new(modules(container, computer, applied) + sections))
62
- Report.new(applied:, ignored: ignored + left_out(container))
81
+ Report.new(applied:, ignored: ignored + left_out(container, names, applied))
82
+ end
83
+
84
+ def read_names(computer) = computer.superpet? ? READ + SUPERPET_READ : READ
85
+
86
+ # The disks badline's true drive has, which stay in the drive the
87
+ # snapshot puts on the bus when it is of the same model, or of the
88
+ # same xpet type.
89
+ def kept_disks(container, computer, disks)
90
+ drive = computer.true_drive
91
+ return [] if drive.nil?
92
+
93
+ if Drive8050.present?(container)
94
+ Drive8050.same_type?(Drive8050.model(container), drive.model_name) ? disks : []
95
+ else
96
+ drive.model_name == "2031" ? disks.first(1) : []
97
+ end
98
+ end
99
+
100
+ # The true drive's modules where the snapshot has one badline
101
+ # reads, or none.
102
+ def drive(container, applied, ignored, disks)
103
+ return drive8050(container, applied, ignored, disks) if Drive8050.present?(container)
104
+ return [] unless Drive2031.present?(container)
105
+
106
+ names = [Drive2031::UNITS.first, Drive2031::CPU, Drive2031::VIA1, Drive2031::VIA2]
107
+ return [] unless readable?(container, names, "2031", ignored) { |section| Drive2031.reads?(section) }
108
+
109
+ applied.concat(names)
110
+ Drive2031.import(container, disks.first)
111
+ end
112
+
113
+ def drive8050(container, applied, ignored, disks)
114
+ model = Drive8050.model(container)
115
+ return [] unless readable?(container, Drive8050::NAMES, model || "8050", ignored) do |section|
116
+ Drive8050.reads?(section)
117
+ end
118
+
119
+ if model.nil?
120
+ ignored << "the true drive: badline doesn't read its #{Drive8050::UNIT}'s type, left out"
121
+ return []
122
+ end
123
+
124
+ applied.concat(Drive8050::NAMES)
125
+ model = Drive8050.hard_disk_model(disks) if Drive8050.hard_disk?(model)
126
+ Drive8050.import(container, Drive8050.reference(model), disks)
127
+ end
128
+
129
+ # Whether the snapshot has every module `names` lists at a version
130
+ # badline reads, saying which it doesn't in `ignored`.
131
+ def readable?(container, names, model, ignored, &reads)
132
+ unread = names.map { |name| container[name] }.reject { |section| section && reads.call(section) }
133
+ return true if unread.empty?
134
+
135
+ ignored << "the true #{model}: badline doesn't read #{unread.compact.join(', ')}, left out"
136
+ false
63
137
  end
64
138
 
65
139
  def required?(name) = [MainCPU::NAME, PETMem::NAME].include?(name)
@@ -70,6 +144,8 @@ module Badline
70
144
  when PETMem::NAME then PETMem.reads?(section)
71
145
  when CRTC::NAME then CRTC.reads?(section)
72
146
  when VIA::NAME then VIA.reads?(section)
147
+ when CPU6809::NAME then CPU6809.reads?(section)
148
+ when CBM2::ACIA::NAME then CBM2::ACIA.reads?(section)
73
149
  else PIAs.reads?(section)
74
150
  end
75
151
  end
@@ -96,9 +172,26 @@ module Badline
96
172
  if applied.include?(VIA::NAME)
97
173
  sections << Keyed.section("BL-VIA") { |out| VIA.import(container[VIA::NAME], out) }
98
174
  end
175
+ sections += superpet(container, applied) if computer.superpet?
99
176
  sections + video
100
177
  end
101
178
 
179
+ # The SuperPET's board, and its 6809 while it has the bus and its
180
+ # ACIA where the snapshot has them. The 6809 the 6502 has the bus
181
+ # from, and a module left out, stay as the machine has them at
182
+ # power-on.
183
+ def superpet(container, applied)
184
+ cycles = FieldReader.new(container[MainCPU::NAME]).qword
185
+ memory = container[PETMem::NAME]
186
+ sections = [Keyed.section("BL-SUPERPET") { |out| PETMem.import_superpet(memory, out) }]
187
+ if applied.include?(CPU6809::NAME) && PETMem.m6809?(memory)
188
+ sections << Keyed.section("BL-M6809") { |out| CPU6809.import(container[CPU6809::NAME], out, cycles) }
189
+ end
190
+ return sections unless applied.include?(CBM2::ACIA::NAME)
191
+
192
+ sections << Keyed.section("BL-ACIA") { |out| CBM2::ACIA.import(container[CBM2::ACIA::NAME], out, cycles) }
193
+ end
194
+
102
195
  def machine(out, container, computer, drive)
103
196
  out.text("family", "pet")
104
197
  PETSetup.of(computer).write_fields(out)
@@ -135,12 +228,12 @@ module Badline
135
228
  CRTC.import(crtc, out, CRTC.columns(model), CRTC.attributes(model))
136
229
  end
137
230
  cycles = (CRTC::CRTC_LEAD + CRTC.column_of(crtc)) % CRTC.frame_cycles(CRTC.registers(crtc))
138
- fields = CRTC.run_on(computer, Keyed::Reader.parse(counters), cycles)
231
+ fields = CRTC.run_on(CRTC.fresh_crtc(computer), Keyed::Reader.parse(counters), cycles)
139
232
  [Keyed.section("BL-CRTC") { |out| CRTC.copy(fields, out) }]
140
233
  end
141
234
 
142
- def left_out(container)
143
- container.sections.reject { |section| READ.include?(section.name) }
235
+ def left_out(container, names, applied)
236
+ container.sections.reject { |section| names.include?(section.name) || applied.include?(section.name) }
144
237
  .map { |section| "#{section}: badline doesn't read this module, left out" }
145
238
  end
146
239
  end
@@ -44,8 +44,8 @@ module Badline
44
44
 
45
45
  def reads?(section) = section.major == MAJOR && section.minor == MINOR
46
46
 
47
- # The VIA's module, from its BL-VIA fields in `input`.
48
- def export(input)
47
+ # The VIA's module, under `name`, from its BL-VIA fields in `input`.
48
+ def export(input, name = NAME)
49
49
  fields = FieldWriter.new
50
50
  %w[ora ddra orb ddrb].each { |key| fields.byte(input.int(key, 0)) }
51
51
  fields.word(input.int("t1.latch", 0xffff)).word(timer1(input))
@@ -58,7 +58,7 @@ module Badline
58
58
  fields.byte(input.bool("t1.pb7", true) ? 0x80 : 0)
59
59
  fields.byte(ticks(input))
60
60
  fields.byte(LEVELS | (input.bool("ca.c2_output", true) ? 0x80 : 0)).zeros(2)
61
- fields.byte(input.bool("t2.armed", false) ? 1 : 0).zeros(2).section(NAME, MAJOR, MINOR)
61
+ fields.byte(input.bool("t2.armed", false) ? 1 : 0).zeros(2).section(name, MAJOR, MINOR)
62
62
  end
63
63
 
64
64
  # Timer 1's counter as a read on the next cycle finds it: as it
@@ -96,13 +96,14 @@ module Badline
96
96
  end
97
97
 
98
98
  # The shift register from its mode, data and the ticks into its
99
- # byte (#ticks), with the last bit put out on CB2.
99
+ # byte (#ticks), with the last bit put out on CB2, or CB2 high
100
+ # as it is at power-on while the register is off.
100
101
  def shift_register(out, mode, data, ticks)
101
102
  ticks = [ticks, TICKS].min
102
103
  bits = ticks == TICKS ? 0 : 8 - (ticks / 2)
103
104
  bits = 8 if mode == 4 && ticks < TICKS
104
105
  out.int("sr.mode", mode).int("sr.data", data).int("sr.bits", bits).int("sr.delay", 0)
105
- out.bool("sr.clock", ticks.even?).bool("sr.cb2", ticks.zero? || data.anybits?(0x01))
106
+ out.bool("sr.clock", ticks.even?).bool("sr.cb2", mode.zero? || ticks.zero? || data.anybits?(0x01))
106
107
  end
107
108
 
108
109
  # Whether the shift register clocks off timer 2.
@@ -38,7 +38,8 @@ module Badline
38
38
 
39
39
  def import(section, computer)
40
40
  registers = FieldReader.new(section).skip(4).bytes(0x20)
41
- registers.first(0x19).each_with_index { |value, reg| computer.sid.poke(0xd400 + reg, value) }
41
+ sid = computer.sid
42
+ registers.first(0x19).each_with_index { |value, reg| sid.poke(sid.start + reg, value) }
42
43
  end
43
44
  end
44
45
  end