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
@@ -5,14 +5,13 @@ require "badline/chips/vic/registers"
5
5
  require "badline/chips/vic/display_state"
6
6
  require "badline/chips/vic/sequencer"
7
7
  require "badline/chips/vic/sprites"
8
- require "badline/chips/vic/saved_state"
8
+ require "badline/chips/vic/keyed_parts"
9
9
 
10
10
  module Badline
11
11
  module Chips
12
12
  class VIC
13
13
  include Addressable
14
14
  include IntegerHelper
15
- include SavedState
16
15
 
17
16
  # The 6569 of the breadbin C64, the HMOS 8565 of the C64C, and the
18
17
  # C128's VIC-IIe, the 8566 (PAL) and 8564 (NTSC). The VIC-IIe draws as
@@ -229,6 +228,11 @@ module Badline
229
228
  @sequencer.start_vertical_border(@rasterline == @last_line ? 0 : @rasterline + 1) if hooks.anybits?(HOOK_LAST)
230
229
  end
231
230
 
231
+ # Latches the sprite collisions up to the beam, as a read of $D01E or
232
+ # $D01F would, so the registers stand as the hardware has them when a
233
+ # snapshot is saved.
234
+ def fold_collisions! = @sprites.collide_upto(@column * 8, @sequencer.fg)
235
+
232
236
  # The IRQ line is held asserted while any enabled latch bit is set in
233
237
  # $D019/$D01A, until the program acknowledges it by writing to $D019.
234
238
  def interrupted?
@@ -0,0 +1,142 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Badline
4
+ module Chips
5
+ class VICI
6
+ class Sound
7
+ # The sound's fields in a keyed snapshot module, under `prefix`:
8
+ # the voices' shift registers, the noise LFSR, the level, and when
9
+ # each voice next shifts, in cycles from the machine's clock, with
10
+ # how far behind the clock the voices last ran. The volume and the
11
+ # voices' frequencies come from the VIC's registers on load.
12
+ module KeyedState
13
+ def save_fields(out, prefix)
14
+ now = @clock.cycles
15
+ run_to(now)
16
+ out.ints("#{prefix}shift_registers", @shift_registers).int("#{prefix}lfsr", @lfsr)
17
+ out.int("#{prefix}level", @level).int("#{prefix}ran_ago", now - @time)
18
+ out.ints("#{prefix}next_shifts", @next_shifts.map { |due| due - now })
19
+ end
20
+
21
+ def load_fields(input, prefix)
22
+ now = @clock.cycles
23
+ input.ints_into("#{prefix}shift_registers", @shift_registers)
24
+ @lfsr = input.masked("#{prefix}lfsr", @lfsr, 0xffff)
25
+ @level = input.int("#{prefix}level", @level)
26
+ @time = now - input.int("#{prefix}ran_ago", now - @time)
27
+ relative = input.ints("#{prefix}next_shifts", @next_shifts.map { |due| due - now })
28
+ VOICES.times { |voice| @next_shifts[voice] = now + relative.fetch(voice, 0) }
29
+ end
30
+
31
+ # $900A to $900E as written.
32
+ def restore_registers(registers)
33
+ @volume = registers[0x0e] & 0x0f
34
+ VOICES.times do |voice|
35
+ value = registers[0x0a + voice]
36
+ @frequencies[voice] = value & 0x7f
37
+ @enabled[voice] = value >= 0x80
38
+ end
39
+ end
40
+ end
41
+
42
+ include KeyedState
43
+ end
44
+
45
+ class Painter
46
+ # The lines' fetches and the line going out, as this painter lays
47
+ # them out (VICI::LINE_LAYOUT), and the picture.
48
+ module KeyedState
49
+ def save_fields(out)
50
+ out.int("line.row", @row).int("line.painted", @painted).bool("line.written", @written)
51
+ out.bytes("line.patterns", @patterns).bytes("line.colors", @colors)
52
+ out.ints("line.counts", @counts).ints("line.starts", @starts).bytes("display", @display)
53
+ end
54
+
55
+ def load_fields(input, layout)
56
+ input.bytes_into("display", @display)
57
+ return unless layout
58
+
59
+ @row = input.int("line.row", @row)
60
+ @painted = input.int("line.painted", @painted)
61
+ @written = input.bool("line.written", @written)
62
+ input.bytes_into("line.patterns", @patterns)
63
+ input.bytes_into("line.colors", @colors)
64
+ input.ints_into("line.counts", @counts)
65
+ input.ints_into("line.starts", @starts)
66
+ end
67
+
68
+ # The colours as $900E and $900F hold them, every line to be
69
+ # painted afresh, and the whole display changed.
70
+ def restored!(registers)
71
+ @border = registers[0x0f] & 0x07
72
+ @background = registers[0x0f] >> 4
73
+ @reverse = registers[0x0f].nobits?(0x08)
74
+ @aux = registers[0x0e] >> 4
75
+ @stale.fill(true)
76
+ @memo_keys.fill(-1)
77
+ @dirty_lines.fill(true)
78
+ end
79
+ end
80
+
81
+ include KeyedState
82
+ end
83
+
84
+ # The shape of the painter's per-line fields. A snapshot whose
85
+ # line.layout differs leaves them at power-on.
86
+ LINE_LAYOUT = 1
87
+
88
+ # The VIC-I's fields in a keyed snapshot module (Snapshot::Keyed): the
89
+ # registers, the beam, the text window's counters and the fetch in
90
+ # flight, the painter's lines under line.*, the picture, and the sound
91
+ # under sound.* What the registers decode to, where the line's fetches
92
+ # end and the next event are worked out on load.
93
+ module KeyedState
94
+ def save_fields(out)
95
+ out.bytes("registers", @registers).int("raster", @rasterline).int("column", @column)
96
+ out.bool("window_open", @window_open).bool("window_done", @window_done).int("row_start", @row_start)
97
+ out.int("matrix", @matrix).int("char_line", @char_line).int("text_rows", @text_rows)
98
+ out.int("fetch_from", @fetch_from).int("fetched", @fetched).int("code", @code)
99
+ out.int("line.layout", LINE_LAYOUT)
100
+ @painter.save_fields(out)
101
+ @sound&.save_fields(out, "sound.")
102
+ out
103
+ end
104
+
105
+ def load_fields(input)
106
+ input.bytes_into("registers", @registers)
107
+ @rasterline = input.int("raster", @rasterline)
108
+ @column = input.int("column", @column)
109
+ @window_open = input.bool("window_open", @window_open)
110
+ @window_done = input.bool("window_done", @window_done)
111
+ @row_start = input.int("row_start", @row_start)
112
+ @matrix = input.int("matrix", @matrix)
113
+ @char_line = input.int("char_line", @char_line)
114
+ @text_rows = input.int("text_rows", @text_rows)
115
+ @fetch_from = input.int("fetch_from", @fetch_from)
116
+ @fetched = input.int("fetched", @fetched)
117
+ @code = input.byte("code", @code)
118
+ line = input.int("line.layout", 0) == LINE_LAYOUT
119
+ input.adjust("line in progress from another renderer, restarted") unless line || input.missing?
120
+ @painter.load_fields(input, line)
121
+ @sound&.load_fields(input, "sound.")
122
+ end
123
+
124
+ def restored!
125
+ @origin_x = @registers[0] & 0x7f
126
+ @columns = @registers[2] & 0x7f
127
+ @origin_y = @registers[1]
128
+ rows_written(@registers[3])
129
+ bases_written
130
+ @slot = @rasterline * Painter::SLOTS
131
+ @fetch_to = 0
132
+ fetches_end if @window_open
133
+ schedule
134
+ @painter.restored!(@registers)
135
+ @sound&.restore_registers(@registers)
136
+ end
137
+ end
138
+
139
+ include KeyedState
140
+ end
141
+ end
142
+ end
@@ -1,7 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "badline/chips/vic_i/painter/saved_state"
4
-
5
3
  module Badline
6
4
  module Chips
7
5
  class VICI
@@ -28,8 +26,6 @@ module Badline
28
26
  # nibble's low three bits) and 11 the auxiliary colour, whatever
29
27
  # $900F bit 3 says (MOS 6560/6561 datasheet, "Color operating modes").
30
28
  class Painter
31
- include SavedState
32
-
33
29
  # The characters a line holds room for. A line fetches at most 34.
34
30
  SLOTS = 40
35
31
 
@@ -30,10 +30,11 @@ module Badline
30
30
  # The output is the voices that are high, plus a little more, times the
31
31
  # volume, so a volume write steps the output even with every voice off.
32
32
  #
33
- # The voices only run while #record collects the output: nothing the
34
- # CPU reads depends on them. Until then a write only sets the register.
35
- # While recording, the voices catch up to the current cycle whenever a
36
- # write lands or the samples are drained, one shift at a time.
33
+ # The voices catch up to the current cycle whenever a write lands, the
34
+ # samples are drained or #catch_up! asks, one shift at a time, whether
35
+ # or not #record collects the output, so their state doesn't depend on
36
+ # whether the host plays it. Unrecorded, while every voice is off and
37
+ # shifted out to silence, they skip to the cycle at once.
37
38
  class Sound
38
39
  VOICES = 4
39
40
  NOISE = 3
@@ -86,14 +87,15 @@ module Badline
86
87
  # second, for #drain_samples. `clock_hz` is the rate the cycles are
87
88
  # taken to run at.
88
89
  def record(rate:, clock_hz: @clock_hz)
89
- now = @clock.cycles
90
- run_to(now)
91
- resume(now)
90
+ run_to(@clock.cycles)
92
91
  @output = Output.new(clock_hz:, rate:, level: @level)
93
92
  end
94
93
 
95
94
  def recording? = !@output.nil?
96
95
 
96
+ # Runs the voices up to the current cycle.
97
+ def catch_up! = run_to(@clock.cycles)
98
+
97
99
  # The samples recorded since the last drain.
98
100
  def drain_samples
99
101
  output = @output
@@ -116,29 +118,7 @@ module Badline
116
118
  @level = level_now
117
119
  end
118
120
 
119
- # The registers and the voices as they last ran, for a snapshot.
120
- # Recording is the host's, and starts the voices again from the
121
- # cycle it starts at.
122
- def save_state(out)
123
- run_to(@clock.cycles)
124
- out.marker("VIC20 SOUND").int(@volume).ints(@frequencies).booleans(@enabled)
125
- out.ints(@shift_registers).ints(@next_shifts).int(@lfsr).int(@level).int(@time)
126
- end
127
-
128
- def load_state(input)
129
- input.marker("VIC20 SOUND")
130
- @volume = input.int
131
- input.ints_into(@frequencies)
132
- input.booleans_into(@enabled)
133
- input.ints_into(@shift_registers)
134
- input.ints_into(@next_shifts)
135
- @lfsr = input.int
136
- @level = input.int
137
- @time = input.int
138
- resume(@clock.cycles) if recording?
139
- end
140
-
141
- # A voice's shift register as of the current cycle, while recording.
121
+ # A voice's shift register as of the current cycle.
142
122
  def shift_register(voice)
143
123
  run_to(@clock.cycles)
144
124
  @shift_registers[voice]
@@ -151,35 +131,47 @@ module Badline
151
131
 
152
132
  private
153
133
 
154
- # Moves each shift that the cycles since the voices last ran have
155
- # passed to its next turn from `now`, keeping its phase.
156
- def resume(now)
157
- @time = now
158
- VOICES.times do |voice|
159
- due = @next_shifts[voice]
160
- next if due >= now
161
-
162
- interval = period(voice) * DIVIDERS[voice]
163
- @next_shifts[voice] = due + ((now - due + interval - 1) / interval * interval)
164
- end
165
- end
166
-
167
134
  # Runs the voices up to the start of cycle `target`, the output holding
168
135
  # each level for the cycles it lasts.
169
136
  def run_to(target)
170
137
  time = @time
138
+ return if target <= time
139
+
171
140
  output = @output
172
- return if output.nil? || target <= time
141
+ return skip_to(target) if output.nil? && silent?
173
142
 
174
143
  following = next_shift
175
144
  while following < target
176
- output.hold(@level, following - time)
145
+ output&.hold(@level, following - time)
177
146
  time = following
178
147
  shift_due(time)
179
148
  @level = level_now
180
149
  following = next_shift
181
150
  end
182
- output.hold(@level, target - time)
151
+ output&.hold(@level, target - time)
152
+ @time = target
153
+ end
154
+
155
+ # Whether every voice is off and shifted out to where its shifts
156
+ # change nothing: the tone voices' registers empty and the noise
157
+ # voice's LFSR full of ones.
158
+ def silent?
159
+ enabled = @enabled
160
+ registers = @shift_registers
161
+ !(enabled[0] || enabled[1] || enabled[2] || enabled[NOISE]) &&
162
+ registers[0].zero? && registers[1].zero? && registers[2].zero? && @lfsr == 0xffff
163
+ end
164
+
165
+ # Moves each shift before `target` to its next turn from there,
166
+ # keeping its phase.
167
+ def skip_to(target)
168
+ VOICES.times do |voice|
169
+ due = @next_shifts[voice]
170
+ next if due >= target
171
+
172
+ interval = period(voice) * DIVIDERS[voice]
173
+ @next_shifts[voice] = due + ((target - due + interval - 1) / interval * interval)
174
+ end
183
175
  @time = target
184
176
  end
185
177
 
@@ -2,7 +2,8 @@
2
2
 
3
3
  require "badline/chips/vic_i/painter"
4
4
  require "badline/chips/vic_i/text_window"
5
- require "badline/chips/vic_i/saved_state"
5
+ require "badline/chips/vic_i/sound"
6
+ require "badline/chips/vic_i/keyed_parts"
6
7
 
7
8
  module Badline
8
9
  module Chips
@@ -41,7 +42,6 @@ module Badline
41
42
  # into the line lands 48 pixels in. Lines 0-27 are blanked.
42
43
  class VICI
43
44
  include TextWindow
44
- include SavedState
45
45
 
46
46
  # What a VIC-I sets for the machine: the clock, the raster's cycles
47
47
  # per line and lines per frame, and how many lines at the top of the
@@ -14,7 +14,7 @@ require "badline/chips/z80/extended_page"
14
14
  require "badline/chips/z80/block_transfers"
15
15
  require "badline/chips/z80/block_io"
16
16
  require "badline/chips/z80/interrupt_acceptance"
17
- require "badline/chips/z80/saved_state"
17
+ require "badline/chips/z80/keyed_state"
18
18
 
19
19
  module Badline
20
20
  module Chips
@@ -44,7 +44,7 @@ module Badline
44
44
  include BlockTransfers
45
45
  include BlockIO
46
46
  include InterruptAcceptance
47
- include SavedState
47
+ include KeyedState
48
48
 
49
49
  attr_reader :bus, :cycles, :nmi
50
50
  attr_accessor :int
@@ -0,0 +1,73 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Badline
4
+ module Chips
5
+ class Z80
6
+ # The Z80's fields in a keyed snapshot module (Snapshot::Keyed),
7
+ # between instructions, where it always is after the C128's cycle:
8
+ # the registers, the T-state count and the interrupt latches.
9
+ module KeyedState
10
+ def save_fields(out)
11
+ out.int("a", @a).int("f", @f).int("b", @b).int("c", @c).int("d", @d).int("e", @e).int("h", @h).int("l", @l)
12
+ out.int("af_alt", @af_alt).int("bc_alt", @bc_alt).int("de_alt", @de_alt).int("hl_alt", @hl_alt)
13
+ out.int("i", @i).int("r", @r).int("pc", @pc).int("sp", @sp).int("ix", @ix).int("iy", @iy).int("wz", @wz)
14
+ out.int("q", @q).int("last_q", @last_q).int("im", @im).int("cycles", @cycles)
15
+ out.bool("iff1", @iff1).bool("iff2", @iff2).bool("halted", @halted).bool("after_ei", @after_ei)
16
+ out.bool("after_ld_a_ir", @after_ld_a_ir).bool("int", @int).bool("nmi", @nmi)
17
+ out.bool("nmi_pending", @nmi_pending)
18
+ end
19
+
20
+ def load_fields(input)
21
+ load_main_fields(input)
22
+ load_special_fields(input)
23
+ @cycles = input.int("cycles", @cycles)
24
+ load_latch_fields(input)
25
+ @prefix = 0
26
+ end
27
+
28
+ def restored! = nil
29
+
30
+ private
31
+
32
+ def load_main_fields(input)
33
+ @a = input.byte("a", @a)
34
+ @f = input.byte("f", @f)
35
+ @b = input.byte("b", @b)
36
+ @c = input.byte("c", @c)
37
+ @d = input.byte("d", @d)
38
+ @e = input.byte("e", @e)
39
+ @h = input.byte("h", @h)
40
+ @l = input.byte("l", @l)
41
+ @af_alt = input.word("af_alt", @af_alt)
42
+ @bc_alt = input.word("bc_alt", @bc_alt)
43
+ @de_alt = input.word("de_alt", @de_alt)
44
+ @hl_alt = input.word("hl_alt", @hl_alt)
45
+ end
46
+
47
+ def load_special_fields(input)
48
+ @i = input.byte("i", @i)
49
+ @r = input.byte("r", @r)
50
+ @pc = input.word("pc", @pc)
51
+ @sp = input.word("sp", @sp)
52
+ @ix = input.word("ix", @ix)
53
+ @iy = input.word("iy", @iy)
54
+ @wz = input.word("wz", @wz)
55
+ @q = input.byte("q", @q)
56
+ @last_q = input.byte("last_q", @last_q)
57
+ @im = input.int("im", @im)
58
+ end
59
+
60
+ def load_latch_fields(input)
61
+ @iff1 = input.bool("iff1", @iff1)
62
+ @iff2 = input.bool("iff2", @iff2)
63
+ @halted = input.bool("halted", @halted)
64
+ @after_ei = input.bool("after_ei", @after_ei)
65
+ @after_ld_a_ir = input.bool("after_ld_a_ir", @after_ld_a_ir)
66
+ @int = input.bool("int", @int)
67
+ @nmi = input.bool("nmi", @nmi)
68
+ @nmi_pending = input.bool("nmi_pending", @nmi_pending)
69
+ end
70
+ end
71
+ end
72
+ end
73
+ end
@@ -0,0 +1,10 @@
1
+ # frozen_string_literal: true
2
+
3
+ # The chips badline/core's machines share. The C128's own, the Z80 and the
4
+ # VDC, stay out of the core: badline/c128 requires them.
5
+ require "badline/chips/cpu"
6
+ require "badline/chips/cia"
7
+ require "badline/chips/via"
8
+ require "badline/chips/sid"
9
+ require "badline/chips/vic"
10
+ require "badline/chips/vic_i"
data/lib/badline/core.rb CHANGED
@@ -15,29 +15,13 @@ require "badline/memory"
15
15
  require "badline/c64/color_memory"
16
16
  require "badline/rom"
17
17
  require "badline/c64/banked_ram"
18
- require "badline/chips/cpu/port"
19
- require "badline/c64/bus"
20
- require "badline/chips/cpu/instruction"
21
- require "badline/chips/cpu/instruction_set"
22
18
  require "badline/status"
23
19
  require "badline/input"
24
- require "badline/drives/datasette"
25
- require "badline/chips/cia/time_of_day"
26
- require "badline/chips/cia"
27
- require "badline/chips/via"
28
- require "badline/chips/sid"
29
- require "badline/debug_register"
20
+ require "badline/chips"
21
+ require "badline/drives"
22
+ require "badline/c64/bus"
30
23
  require "badline/c64/reu"
31
- require "badline/chips/cpu/interrupts"
32
- require "badline/chips/cpu/traps"
33
- require "badline/chips/cpu"
34
- require "badline/chips/vic"
35
- require "badline/keyboard_buffer"
36
- require "badline/drives/serial_bus"
37
- require "badline/drives/model1541"
38
- require "badline/drives/model1581"
39
- require "badline/drives/model1581/slot"
40
- require "badline/snapshot/state"
24
+ require "badline/snapshot/keyed"
41
25
  require "badline/snapshot/c64_setup"
42
26
  require "badline/snapshot/storage_setup"
43
27
  require "badline/machine"
@@ -47,5 +31,4 @@ require "badline/storage"
47
31
  require "badline/sid_tune"
48
32
  require "badline/c64/cartridge"
49
33
  require "badline/kernal_trap"
50
- require "badline/chrout_trap"
51
34
  require "badline/media"
@@ -0,0 +1,43 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Badline
4
+ module Drives
5
+ class Datasette
6
+ # The datasette's fields in a keyed snapshot module (Snapshot::Keyed,
7
+ # BL-TAPE): the keys, the motor, the countdown to the next pulse, and
8
+ # a tape in it under tape.*: its path, its bytes and how far it has
9
+ # played. A tape that isn't in leaves tape.* out. A datasette that
10
+ # times the read line's level adds when in the countdown the line
11
+ # rises, and whether a recording's low half wave is on the tape. The
12
+ # sense and flag handlers don't fire on load.
13
+ module KeyedState
14
+ def save_fields(out)
15
+ out.bool("playing", @playing).bool("motor", @motor).int("countdown", @countdown)
16
+ out.int("rise_at", @rise_at).bool("low_recorded", @low_recorded) if @level
17
+ tape = @tape
18
+ return out unless tape
19
+
20
+ out.text("tape.path", tape.path.to_s).bytes("tape.bytes", tape.bytes).int("tape.position", tape.position)
21
+ end
22
+
23
+ # The tape goes back in from the bytes the module holds, without its
24
+ # host file, unless the tape in is the same one.
25
+ def load_fields(input)
26
+ @playing = input.bool("playing", @playing)
27
+ @motor = input.bool("motor", @motor)
28
+ @countdown = input.int("countdown", @countdown)
29
+ @rise_at = input.int("rise_at", @rise_at)
30
+ @low_recorded = input.bool("low_recorded", @low_recorded)
31
+ bytes = input.bytes("tape.bytes", "".b).bytes
32
+ return @tape = nil if bytes.empty?
33
+
34
+ path = input.text("tape.path", "")
35
+ @tape = Storage::TAP.new(path, bytes:) unless @tape&.path.to_s == path && @tape&.bytes == bytes
36
+ @tape&.position = input.int("tape.position", 0)
37
+ end
38
+ end
39
+
40
+ include KeyedState
41
+ end
42
+ end
43
+ end
@@ -1,24 +1,24 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "badline/drives/datasette/saved_state"
3
+ require "badline/drives/datasette/keyed_state"
4
4
 
5
5
  module Badline
6
6
  module Drives
7
7
  # The 1530 datasette. Plays a pulse stream into CIA 1's FLAG pin while the
8
8
  # motor runs with a key pressed, and reports the keys through the cassette
9
9
  # sense line on the $01 port. With RECORD down too, it records the
10
- # machine's write line onto the tape instead.
10
+ # machine's write line onto the tape instead. Built with +level: true+,
11
+ # it also times the read line's level, for a machine that reads that
12
+ # (#read_high?) rather than the falling edges.
11
13
  class Datasette
12
- include SavedState
13
-
14
14
  # What inserting a tape raises on a machine without a cassette port.
15
15
  class Missing < ArgumentError; end
16
-
17
16
  attr_reader :tape
18
17
  attr_writer :motor
19
18
 
20
- def initialize(tape = nil)
19
+ def initialize(tape = nil, level: false)
21
20
  @tape = tape
21
+ @level = level
22
22
  @connected = true
23
23
  @playing = false
24
24
  @recording = false
@@ -26,6 +26,8 @@ module Badline
26
26
  @since = 0
27
27
  @motor = false
28
28
  @countdown = 0
29
+ @rise_at = 0
30
+ @low_recorded = false
29
31
  @flag_handler = nil
30
32
  @sense_handler = nil
31
33
  end
@@ -64,6 +66,7 @@ module Badline
64
66
  def rewind
65
67
  @tape&.rewind
66
68
  @countdown = 0
69
+ @rise_at = 0
67
70
  end
68
71
 
69
72
  def play! = press(true)
@@ -76,6 +79,7 @@ module Badline
76
79
  press(true)
77
80
  @recording = true
78
81
  @since = 0
82
+ @low_recorded = false
79
83
  end
80
84
 
81
85
  def recording? = @recording
@@ -83,14 +87,27 @@ module Badline
83
87
  # The machine's tape write line. While the deck records with its motor
84
88
  # running, each rising edge ends a pulse on the tape, as long as the
85
89
  # time since the edge before, and plays back as a falling edge on the
86
- # read line.
90
+ # read line. A datasette built with +level: true+ records a version 2
91
+ # tape's half waves instead: a rising edge ends the low half and a
92
+ # falling edge the high half after it, so the tape plays back the
93
+ # levels it recorded (#read_high?).
87
94
  def write_line=(high)
88
- rose = high && !@write_high
95
+ edge = high != @write_high
89
96
  @write_high = high
90
- return unless rose && @recording && running?
97
+ return unless edge && @recording && running?
98
+
99
+ if @level && @tape.half_waves?
100
+ record_half_wave(high)
101
+ elsif high
102
+ @tape.record_pulse(@since)
103
+ @since = 0
104
+ end
105
+ end
91
106
 
92
- @tape.record_pulse(@since)
93
- @since = 0
107
+ # Sets the write line's level without recording an edge, as a
108
+ # snapshot loads it.
109
+ def write_level=(high)
110
+ @write_high = high
94
111
  end
95
112
 
96
113
  def playing? = @playing
@@ -102,11 +119,22 @@ module Badline
102
119
 
103
120
  def running? = @playing && @motor && !@tape.nil?
104
121
 
122
+ # The tape read line's level, on a datasette built with +level: true+.
123
+ # Each pulse holds it low for its first half and high for its second,
124
+ # up to the falling edge that ends it. A version 2 tape times each
125
+ # half, and a version 0 or 1 tape's pulse plays as two equal halves.
126
+ # It reads high while the tape isn't playing.
127
+ def read_high?
128
+ return true unless running? && !@recording
129
+
130
+ @countdown.positive? && @countdown <= @rise_at
131
+ end
132
+
105
133
  def cycle!
106
134
  return unless running?
107
135
  return @since += 1 if @recording
108
136
 
109
- @countdown = @tape.next_pulse.to_i if @countdown.zero?
137
+ next_pulse if @countdown.zero?
110
138
  return if @countdown.zero?
111
139
 
112
140
  @countdown -= 1
@@ -120,6 +148,28 @@ module Badline
120
148
 
121
149
  private
122
150
 
151
+ def next_pulse
152
+ return @countdown = @tape.next_pulse.to_i unless @level
153
+ return next_half_waves if @tape.half_waves?
154
+
155
+ @countdown = @tape.next_pulse.to_i
156
+ @rise_at = @countdown / 2
157
+ end
158
+
159
+ def next_half_waves
160
+ first = @tape.next_half_wave.to_i
161
+ @rise_at = @tape.next_half_wave.to_i
162
+ @countdown = first + @rise_at
163
+ end
164
+
165
+ # A high half wave goes on the tape only after the low half before it,
166
+ # so the tape starts with a whole pulse.
167
+ def record_half_wave(rose)
168
+ @tape.record_half_wave(@since) if rose || @low_recorded
169
+ @low_recorded = rose
170
+ @since = 0
171
+ end
172
+
123
173
  def press(down)
124
174
  return if down == @playing || !@connected
125
175
 
@@ -86,6 +86,9 @@ module Badline
86
86
  # is the same at the end of every pass the drive sleeps through.
87
87
  def led_on? = @mechanism.led_on?
88
88
 
89
+ # The power LED, which stays lit.
90
+ def power_led_on? = true
91
+
89
92
  # Stores what the head wrote since the motor last stopped in the
90
93
  # disk's image, as the motor stopping does.
91
94
  def flush